diff --git a/doc/CodingStyle.md b/doc/CodingStyle.md index 4424f88..20473d3 100644 --- a/doc/CodingStyle.md +++ b/doc/CodingStyle.md @@ -1,308 +1,314 @@ -# DataV项目编码规范 -一套良好的编码规范如果在团队开发中被遵守,可以降低团队成员之间交流的成本,同时也降低犯错的几率。可以说编码规范只需花费20%的精力来遵循,却可以降低80%的犯低级错误几率。 -这一版的编码规范,主要查考了Douglas Crockford的JSLint的检查条件和过去的一些编码经验,整理而出。 - -## 命名空间 -项目的总命名空间为:**`DataV`**。 -除了Rapheal和D3自身的代码外,我们提交的任何方法都应该挂载在`DataV`命名空间中。 -例外:某些方法和功能可以独立抽取出来,适用于别的项目。但是也应该由一个命名空间来管理它们。 - -## 语法 -### 缩进规范 -由于该项目是普通的前端JavaScript项目,所以缩进采用4个空格的方式。禁止使用tab符号。 -推荐更改编辑器中的设置,使得按tab键可以自动插入4个空格。 -每一层级之间,要保证缩进是正确的。 -### 空格 - -* `=`前后应该存在一个空格 -* `(`前面应该存在一个空格 -* `)`后面应该存在一个空格 -* `{`前面应该存在一个空格 - -### 分号 -每一个申明或者定义,都应该以分号结尾。 - - var username = "Jackson"; - - var foo = function () {}; -每一行调用都应该明确以分号结尾。 - - foo(); - -### 大括号 -请明确使用`{}`来约束代码段。 -推荐: - - if (condition) { - foo = "bar"; - } -不推荐: - - if (condition) boo = "bar"; - -## 命名规范 -### 变量 -采用驼峰首字母小写,后续单词均需大写首字母,不得在中间包含下划线。 -每一个变量都应该通过`var`申明,以防止变量污染。 -正确: - - var pipeChart; - -错误: - - var pipechart; - var pipe_chart; - -### 方法 -方法名同变量名,采用驼峰式命名。 -定义方法均通过`var foo = function () {};`而不是`function foo () {}`。 - - var foo = function () { - // TODO - }; - -方法名应该可以表示方法的行为和暗示方法的返回值。善用`set`、`get`、`is`、`has`等方法前缀。 - -### 类 -由于JavaScript中类和方法的关键字都是`function`,为了区别两者的混淆,类名的定义通常是大写首字母,以表示它是一个作为类来调用的。 - - var Person = function () { - // TODO - }; - -类的方法请尽量赋值在`prototype`属性上,使得通过原型链查找的方式降低内存占用。 -类的数据请尽量在构造函数中赋值,以加快执行时的查找速度。 - - var Person = function (age) { - this.age = age; // 在构造函数中赋值数据 - }; - - // 在原型链中设置方法 - Person.prototype.getAge = function () { - return this.age; - }; - -对于私有方法,在前面添加下划线表示不推荐被外部调用。 - - Persion.prototype._sleep = function () {}; - -### 模块 -为了统一规划,我们的类均需通过模块的定义方式,与命名空间结合,以保证环境的干净。 - - (function () { - var private = "I am private"; - var Person = function () { - this.username = private; - }; - - DataV.Person = Person; - }()); - -## 注释 -注释对于团队而言是十分重要的,每一个方法都应该包含说明。 -必选的注释是方法的说明,方法的参数,和一个例子。 -任何诡异的地方,都应该附有简单的注释来描述它,以提醒后来人注意,以区别对待。 - - /** - * Create chart panel by passin width and height - * @param {string} type Type name - * @param {number} width Chart panel's width value - * @param {number} height Chart panel's height value - * @example - * createChart("pipe", 500, 500); - */ - var createChart = function (type, width, height) { - // Trick comments - trickCall(); - }; - -通过[jsdoc](http://code.google.com/p/jsdoc-toolkit/w/list)的注释,将利于我们后期导出API文档。 - -## 文件名 -文件名的方式也是通过驼峰式,但是以下划线分割单词,一律小写单词。如: - - d3.js - child_process.js - -## JSLint扫描代码 -JSLint的调用方式如下: - - node precommit.js file - -配置方式存放在`config.json`中。 - -## 目录结构说明 -目前datav组件库的目录结构如下: - -``` -JacksonTianmatoMacBook-Pro:datav.js jacksontian$ tree -d -. -├── bin -├── deps -├── doc -├── example -├── lib -└── test -``` -其中详细说明一下: - -- `bin`目录用于存放一些可运行的脚本或者工具,例如`jslint`。 -- `deps`目录用于存放项目的依赖文件,其中有`raphael`和`d3`的相关文件。 -- `doc`目录用于用于存放项目的文档,如API生成文档或一些说明文档。 -- `example`目录存放案例代码。 -- `lib`目录为组件库具体代码,根据每个图的类型不同,存为对应文件。 -- `test`目录存放单元测试代码或自动化测试代码。 - -## 单元测试 -`DataV`项目采用[`QUnit`](http://docs.jquery.com/QUnit)作为测试框架,详细文档请见。 -简单测试例子: - -``` -// 分模块 -module("DataV"); -// 测试用例 -test("Themes.get", function () { - // 单个断言 - equal(DataV.Themes.get("inexsit"), undefined, "Should get undefined when key is inexsit"); - equal(DataV.Themes.get("COLOR_MODE"), "gradient", "Should get gradient when key is COLOR_MODE"); - equal(typeof DataV.Themes.get("COLOR_ARGS"), "object", "COLOR_ARGS should be an object"); - equal(DataV.Themes.get("COLOR_ARGS").length, 2, "COLOR_ARGS should have two items"); -}); -``` - -## 数据格式 -数据格式统一采用二维表的方式,结构比较类似数据库中的表。列名通过文档详细来描述,写在代码的[jsdoc](http://code.google.com/p/jsdoc-toolkit/w/list)的注释中。 -标准数据格式例子如下: - -``` -/** - * 设置数据源 - * Example 举个例字,假设下面的数组表示2个人在一年4个季度的消费。第一个人在4个季度里消费了1、2、3、9元。第二个人消费了3、4、6、3元。 - * [ - * [1,2,3,9], - * [3,4,6,3] - * ] - * @param {Array} source 数据源数组. - */ -Stream.prototype.setSource = function (source) { - // TODO -}; -``` -所有的组件库的调用都是相同的接口`setSource`: - -``` -var stream = new DataV.Stream("chart"); -stream.setSource(source); -stream.render(); -``` -## 事件注入 -每一个组件都可能向外提供一些事件钩子,包括DOM事件,或者业务逻辑事件。绑定的方式十分简单: - -``` -stream.on("click", function (event) { - console.log(event); -}); - -stream.on("dblclick", function (event) { - alert("double click"); -}); - -stream.on("contextmenu", function (event) { - alert("mouse right"); -}); -``` - -组件的内部实现是通过继承`EventProxy`提供自定义方式,在创建画布后,就绑定一些必要的事件到画布节点上,然后将事件触发出去。如果用户如上文,侦听了这些业务事件,将会调用执行。 - -``` -Stream.prototype.createCanvas = function () { - var conf = this.defaults; - this.canvas = Raphael(this.node, conf.width, conf.height); - this.DOMNode = $(this.canvas.canvas); - var that = this; - this.DOMNode.click(function (event) { - that.trigger("click", event); - }); - this.DOMNode.dblclick(function (event) { - that.trigger("dblclick", event); - }); - this.DOMNode.bind("contextmenu", function (event) { - that.trigger("contextmenu", event); - }); - this.DOMNode.delegate("path", "click", function (event) { - that.trigger("path_click", event); - }); -}; -``` - -## 常见错误 -### 判断一个对象是否是数组 -下面是宁朗写的: - -``` -if (color.constructor !== Array) { - throw new Error("The color should be Array"); -} -``` -下面是Underscore的方法: - -``` -_.isArray = nativeIsArray || function(obj) { - return toString.call(obj) == '[object Array]'; -}; -``` -### 数组去重 - -``` -// make an array's every element unique by delete other same element -Array.prototype.uniq = function () { - var temp = {}, - len = this.length; - - for (var i = 0; i < len; i++) { - if (typeof temp[this[i]] == "undefined") { - temp[this[i]] = 1; - } - } - this.length = 0; - len = 0; - for (var i in temp) { - this[len++] = i; - } - return this; -}; -``` -这里可以调用underscore的uniq方法即可。节省N行代码量 - -### 元素是否存在数组中 - -``` -//check if a string is in an array -var _strInArray = function (str, array) { - var i = 0, - l = 0; - for (i = 0, l = array.length; i < l; i++) { - if (array[i] === str) { - return true; - } - } - return false; -}; -``` -换用`_.indexOf(array, value)`可以节省代码量,且返回值比boolean值更有价值 - -### meta - - - -希望大家潮一点,别在写上面那行代码了。下面的表达方式更简洁,且无任何副作用。 - - - -### script标签的type属性 - - - -如果是默认的text/javascript的话,就可以直接省略掉。变成下面这样子: - +# DataV项目编码规范 +一套良好的编码规范如果在团队开发中被遵守,可以降低团队成员之间交流的成本,同时也降低犯错的几率。可以说编码规范只需花费20%的精力来遵循,却可以降低80%的犯低级错误几率。 +这一版的编码规范,主要查考了Douglas Crockford的JSLint的检查条件和过去的一些编码经验,整理而出。 + +## 命名空间 +项目的总命名空间为:**`DataV`**。 +除了Rapheal和D3自身的代码外,我们提交的任何方法都应该挂载在`DataV`命名空间中。 +例外:某些方法和功能可以独立抽取出来,适用于别的项目。但是也应该由一个命名空间来管理它们。 + +## 语法 +### 缩进规范 +由于该项目是普通的前端JavaScript项目,所以缩进采用2个空格的方式。禁止使用tab符号。 +推荐更改编辑器中的设置,使得按tab键可以自动插入2个空格。 +每一层级之间,要保证缩进是正确的。 +### 空格 + +* `=`前后应该存在一个空格 +* `(`前面应该存在一个空格 +* `)`后面应该存在一个空格 +* `{`前面应该存在一个空格 + +### 分号 +每一个申明或者定义,都应该以分号结尾。 + + var username = "Jackson"; + + var foo = function () {}; +每一行调用都应该明确以分号结尾。 + + foo(); + +### 大括号 +请明确使用`{}`来约束代码段。 +推荐: + + if (condition) { + foo = "bar"; + } +不推荐: + + if (condition) boo = "bar"; + +## 命名规范 +### 变量 +采用驼峰首字母小写,后续单词均需大写首字母,不得在中间包含下划线。 +每一个变量都应该通过`var`申明,以防止变量污染。 +正确: + + var pipeChart; + +错误: + + var pipechart; + var pipe_chart; + +### 方法 +方法名同变量名,采用驼峰式命名。 +定义方法均通过`var foo = function () {};`而不是`function foo () {}`。 + + var foo = function () { + // TODO + }; + +方法名应该可以表示方法的行为和暗示方法的返回值。善用`set`、`get`、`is`、`has`等方法前缀。 + +### 类 +由于JavaScript中类和方法的关键字都是`function`,为了区别两者的混淆,类名的定义通常是大写首字母,以表示它是一个作为类来调用的。 + + var Person = function () { + // TODO + }; + +类的方法请尽量赋值在`prototype`属性上,使得通过原型链查找的方式降低内存占用。 +类的数据请尽量在构造函数中赋值,以加快执行时的查找速度。 + + var Person = function (age) { + this.age = age; // 在构造函数中赋值数据 + }; + + // 在原型链中设置方法 + Person.prototype.getAge = function () { + return this.age; + }; + +对于私有方法,在前面添加下划线表示不推荐被外部调用。 + + Persion.prototype._sleep = function () {}; + +### 模块 +为了统一规划,我们的类均需通过模块的定义方式,与命名空间结合,以保证环境的干净。 + + (function () { + var private = "I am private"; + var Person = function () { + this.username = private; + }; + + DataV.Person = Person; + }()); + +## 注释 +注释对于团队而言是十分重要的,每一个方法都应该包含说明。 +必选的注释是方法的说明,方法的参数,和一个例子。 +任何诡异的地方,都应该附有简单的注释来描述它,以提醒后来人注意,以区别对待。 + + /** + * Create chart panel by passin width and height + * @param {string} type Type name + * @param {number} width Chart panel's width value + * @param {number} height Chart panel's height value + * @example + * createChart("pipe", 500, 500); + */ + var createChart = function (type, width, height) { + // Trick comments + trickCall(); + }; + +通过[jsdoc](http://code.google.com/p/jsdoc-toolkit/w/list)的注释,将利于我们后期导出API文档。 + +## 文件名 +文件名的方式也是通过驼峰式,但是以下划线分割单词,一律小写单词。如: + + d3.js + child_process.js + +## 文件内容 +### 换行符 +请用Unix的CRLF换行符 +### 文件编码 +请用UTF-8 without BOM作为文件内容的编码 + +## JSLint扫描代码 +JSLint的调用方式如下: + + node precommit.js file + +配置方式存放在`config.json`中。 + +## 目录结构说明 +目前datav组件库的目录结构如下: + +``` +JacksonTianmatoMacBook-Pro:datav.js jacksontian$ tree -d +. +├── bin +├── deps +├── doc +├── example +├── lib +└── test +``` +其中详细说明一下: + +- `bin`目录用于存放一些可运行的脚本或者工具,例如`jslint`。 +- `deps`目录用于存放项目的依赖文件,其中有`raphael`和`d3`的相关文件。 +- `doc`目录用于用于存放项目的文档,如API生成文档或一些说明文档。 +- `example`目录存放案例代码。 +- `lib`目录为组件库具体代码,根据每个图的类型不同,存为对应文件。 +- `test`目录存放单元测试代码或自动化测试代码。 + +## 单元测试 +`DataV`项目采用[`QUnit`](http://docs.jquery.com/QUnit)作为测试框架,详细文档请见。 +简单测试例子: + +``` +// 分模块 +module("DataV"); +// 测试用例 +test("Themes.get", function () { + // 单个断言 + equal(DataV.Themes.get("inexsit"), undefined, "Should get undefined when key is inexsit"); + equal(DataV.Themes.get("COLOR_MODE"), "gradient", "Should get gradient when key is COLOR_MODE"); + equal(typeof DataV.Themes.get("COLOR_ARGS"), "object", "COLOR_ARGS should be an object"); + equal(DataV.Themes.get("COLOR_ARGS").length, 2, "COLOR_ARGS should have two items"); +}); +``` + +## 数据格式 +数据格式统一采用二维表的方式,结构比较类似数据库中的表。列名通过文档详细来描述,写在代码的[jsdoc](http://code.google.com/p/jsdoc-toolkit/w/list)的注释中。 +标准数据格式例子如下: + +``` +/** + * 设置数据源 + * Example 举个例字,假设下面的数组表示2个人在一年4个季度的消费。第一个人在4个季度里消费了1、2、3、9元。第二个人消费了3、4、6、3元。 + * [ + * [1,2,3,9], + * [3,4,6,3] + * ] + * @param {Array} source 数据源数组. + */ +Stream.prototype.setSource = function (source) { + // TODO +}; +``` +所有的组件库的调用都是相同的接口`setSource`: + +``` +var stream = new DataV.Stream("chart"); +stream.setSource(source); +stream.render(); +``` +## 事件注入 +每一个组件都可能向外提供一些事件钩子,包括DOM事件,或者业务逻辑事件。绑定的方式十分简单: + +``` +stream.on("click", function (event) { + console.log(event); +}); + +stream.on("dblclick", function (event) { + alert("double click"); +}); + +stream.on("contextmenu", function (event) { + alert("mouse right"); +}); +``` + +组件的内部实现是通过继承`EventProxy`提供自定义方式,在创建画布后,就绑定一些必要的事件到画布节点上,然后将事件触发出去。如果用户如上文,侦听了这些业务事件,将会调用执行。 + +``` +Stream.prototype.createCanvas = function () { + var conf = this.defaults; + this.canvas = Raphael(this.node, conf.width, conf.height); + this.DOMNode = $(this.canvas.canvas); + var that = this; + this.DOMNode.click(function (event) { + that.trigger("click", event); + }); + this.DOMNode.dblclick(function (event) { + that.trigger("dblclick", event); + }); + this.DOMNode.bind("contextmenu", function (event) { + that.trigger("contextmenu", event); + }); + this.DOMNode.delegate("path", "click", function (event) { + that.trigger("path_click", event); + }); +}; +``` + +## 常见错误 +### 判断一个对象是否是数组 +下面是宁朗写的: + +``` +if (color.constructor !== Array) { + throw new Error("The color should be Array"); +} +``` +下面是Underscore的方法: + +``` +_.isArray = nativeIsArray || function(obj) { + return toString.call(obj) == '[object Array]'; +}; +``` +### 数组去重 + +``` +// make an array's every element unique by delete other same element +Array.prototype.uniq = function () { + var temp = {}, + len = this.length; + + for (var i = 0; i < len; i++) { + if (typeof temp[this[i]] == "undefined") { + temp[this[i]] = 1; + } + } + this.length = 0; + len = 0; + for (var i in temp) { + this[len++] = i; + } + return this; +}; +``` +这里可以调用underscore的uniq方法即可。节省N行代码量 + +### 元素是否存在数组中 + +``` +//check if a string is in an array +var _strInArray = function (str, array) { + var i = 0, + l = 0; + for (i = 0, l = array.length; i < l; i++) { + if (array[i] === str) { + return true; + } + } + return false; +}; +``` +换用`_.indexOf(array, value)`可以节省代码量,且返回值比boolean值更有价值 + +### meta + + + +希望大家潮一点,别在写上面那行代码了。下面的表达方式更简洁,且无任何副作用。 + + + +### script标签的type属性 + + + +如果是默认的text/javascript的话,就可以直接省略掉。变成下面这样子: + \ No newline at end of file diff --git a/lib/charts/axis.js b/lib/charts/axis.js index 6ac321d..0321825 100644 --- a/lib/charts/axis.js +++ b/lib/charts/axis.js @@ -1,326 +1,326 @@ -//copy codes from d3.js, add 4 functions: tickAttr, tickTextAttr, minorTickAttr and domainAttr; -//axis() changes, need a raphael paper object param, return raphael set object. -//examples in ../examples/axis/ to know the usage. -//a basic part for other data visualization format -/*global d3*/ -/*! - * Axis兼容定义 - */ -;(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];}); - } -})('Axis', function (require) { - /** - * function from d3, get scaleRange of an ordinal scale - * @param {Array} domain ordinal scale's range - */ - function d3_scaleExtent(domain) { - var start = domain[0], stop = domain[domain.length - 1]; - return start < stop ? [start, stop] : [stop, start]; - } - - /** - * function from d3, get scaleRange of a scale - */ - function d3_scaleRange(scale) { - return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); - } - - /** - * function from d3, get subticks - * @param scale, scale - * @param ticks, major ticks of scale - * @param m, number of subdivide - */ - function d3_svg_axisSubdivide(scale, ticks, m) { - var subticks = []; - if (m && ticks.length > 1) { - var extent = d3_scaleExtent(scale.domain()), - i = -1, - n = ticks.length, - d = (ticks[1] - ticks[0]) / ++m, - j, - v; - while (++i < n) { - for (j = m; --j > 0;) { - if ((v = +ticks[i] - j * d) >= extent[0]) { - subticks.push(v); - } - } - } - for (--i, j = 0; ++j < m && (v = +ticks[i] + j * d) < extent[1];) { - subticks.push(v); - } - } - return subticks; - } - - var Axis = function () { - var scale = d3.scale.linear(), - orient = "bottom", - tickMajorSize = 6, - tickMinorSize = 6, - tickEndSize = 6, - tickPadding = 3, - tickArguments_ = [10], - tickFormat_, - tickSubdivide = 0, - - tickAttr_ = {}, - tickTextAttr_ = {}, - minorTickAttr_ = {}, - domainAttr_ = {}; - - /** - * @param paper: raphael's paper object. - * @return axisSet: raphael's set object. - */ - function axis(paper) { - // Ticks for quantitative scale, or domain values for ordinal scale. - var ticks = scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain(), - tickFormat = tickFormat_ === undefined ? - (scale.tickFormat ? - scale.tickFormat.apply(scale, tickArguments_) - : String) - : tickFormat_; - - var subticks = d3_svg_axisSubdivide(scale, ticks, tickSubdivide); - var range = d3_scaleRange(scale); - - var axisSet = paper.set(); - - switch (orient) { - case "bottom": - subticks.forEach(function (d, i, arr) { - var tickX = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2; - axisSet.push(paper - .path("M" + tickX + "," + tickMinorSize + "V0") - .attr(minorTickAttr_)); - }); - ticks.forEach(function (d, i, arr) { - var tickX = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2; - axisSet.push(paper - .path("M" + tickX + "," + tickMajorSize + "V0") - .attr(tickAttr_)); - axisSet.push(paper - .text(tickX, Math.max(tickMajorSize, 0) + tickPadding + 2, - typeof tickFormat === "function" ? tickFormat(d) : tickFormat) - .attr({"text-anchor": "middle"}) - .attr(tickTextAttr_)); - }); - axisSet.push(paper - .path("M" + range[0] + "," + tickEndSize + "V0H" + range[1] + "V" + tickEndSize) - .attr(domainAttr_)); - break; - - case "top": - subticks.forEach(function (d, i, arr) { - var tickX = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2; - axisSet.push(paper - .path("M" + tickX + "," + -tickMinorSize + "V0") - .attr(minorTickAttr_)); - }); - ticks.forEach(function (d, i, arr) { - var tickX = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2; - axisSet.push(paper - .path("M" + tickX + "," + -tickMajorSize + "V0") - .attr(tickAttr_)); - axisSet.push(paper - .text(tickX, -(Math.max(tickMajorSize, 0) + tickPadding + 2), - typeof tickFormat === "function" ? tickFormat(d) : tickFormat) - .attr({"text-anchor": "middle"}) - .attr(tickTextAttr_)); - }); - axisSet.push(paper - .path("M" + range[0] + "," + -tickEndSize + "V0H" + range[1] + "V" + -tickEndSize) - .attr(domainAttr_)); - break; - - case "left": - subticks.forEach(function (d, i, arr) { - var tickY = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2; - axisSet.push(paper - .path("M" + -tickMinorSize + "," + tickY + "H0") - .attr(minorTickAttr_)); - }); - ticks.forEach(function (d, i, arr) { - var tickY = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2; - axisSet.push(paper - .path("M" + -tickMajorSize + "," + tickY + "H0") - .attr(tickAttr_)); - axisSet.push(paper - .text(-(Math.max(tickMajorSize, 0) + tickPadding), tickY, - typeof tickFormat === "function" ? tickFormat(d) : tickFormat) - .attr({"text-anchor": "end"}) - .attr(tickTextAttr_)); - }); - axisSet.push(paper - .path("M" + -tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + -tickEndSize) - .attr(domainAttr_)); - break; - - case "right": - subticks.forEach(function (d, i, arr) { - var tickY = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2; - axisSet.push(paper - .path("M" + tickMinorSize + "," + tickY + "H0") - .attr(minorTickAttr_)); - }); - ticks.forEach(function (d, i, arr) { - var tickY = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2; - axisSet.push(paper - .path("M" + tickMajorSize + "," + tickY + "H0") - .attr(tickAttr_)); - axisSet.push(paper - .text(Math.max(tickMajorSize, 0) + tickPadding, tickY, - typeof tickFormat === "function" ? tickFormat(d) : tickFormat) - .attr({"text-anchor": "start"}) - .attr(tickTextAttr_)); - }); - axisSet.push(paper - .path("M" + tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + tickEndSize) - .attr(domainAttr_)); - break; - } - - return axisSet; - } - - /** - * get or set axis' scale. - */ - axis.scale = function (x) { - if (!arguments.length) { - return scale; - } - scale = x; - return axis; - }; - - /** - * get or set axis' orinet: "bottom", "top", "left", "right", default orient is bottom. - */ - axis.orient = function (x) { - if (!arguments.length) { - return orient; - } - orient = x; - return axis; - }; - - /** - * get or set axis' ticks number. - */ - axis.ticks = function () { - if (!arguments.length) { - return tickArguments_; - } - tickArguments_ = arguments; - return axis; - }; - - /** - * get or set axis' ticks format function, it's a function change format style. - * from one string format to another string format. - */ - axis.tickFormat = function (x) { - if (!arguments.length) { - return tickFormat_; - } - tickFormat_ = x; - return axis; - }; - - /** - * get or set axis' tick size(length of tick line, unit: px). - * @param arguments.length === 0, get axis' major tick size. - * @param arguments.length === 1, set axis' all tick sizes as x. - * @param arguments.length === 2, get axis' major tick size as x, minor and end size as y. - * @param arguments.length === 3, get axis' major tick size as x, minor size as y, end size as z. - */ - axis.tickSize = function (x, y, z) { - if (!arguments.length) { - return tickMajorSize; - } - var n = arguments.length - 1; - tickMajorSize = +x; - tickMinorSize = n > 1 ? +y : tickMajorSize; - tickEndSize = n > 0 ? +arguments[n] : tickMajorSize; - return axis; - }; - - /** - * get or set axis' tick padding(the distance between tick text and axis). - * @param x is a number, unit is px; - */ - axis.tickPadding = function (x) { - if (!arguments.length) { - return tickPadding; - } - tickPadding = +x; - return axis; - }; - - /** - * get or set axis' sub tick divide number(divide number between two major ticks). - */ - axis.tickSubdivide = function (x) { - if (!arguments.length) { - return tickSubdivide; - } - tickSubdivide = +x; - return axis; - }; - - /** - * get or set axis' tick attribute(Raphael format). - */ - axis.tickAttr = function (x) { - if (!arguments.length) { - return tickAttr_; - } - tickAttr_ = x; - return axis; - }; - - /** - * get or set axis' tick text attribute(Raphael format). - */ - axis.tickTextAttr = function (x) { - if (!arguments.length) { - return tickTextAttr_; - } - tickTextAttr_ = x; - return axis; - }; - - /** - * get or set axis' minor tick attribute(Raphael format). - */ - axis.minorTickAttr = function (x) { - if (!arguments.length) { - return minorTickAttr_; - } - minorTickAttr_ = x; - return axis; - }; - - /** - * get or set axis' domain(axis line) attribute(Raphael format). - */ - axis.domainAttr = function (x) { - if (!arguments.length) { - return domainAttr_; - } - domainAttr_ = x; - return axis; - }; - - return axis; - }; - - return Axis; -}); +//copy codes from d3.js, add 4 functions: tickAttr, tickTextAttr, minorTickAttr and domainAttr; +//axis() changes, need a raphael paper object param, return raphael set object. +//examples in ../examples/axis/ to know the usage. +//a basic part for other data visualization format +/*global d3*/ +/*! + * Axis兼容定义 + */ +;(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];}); + } +})('Axis', function (require) { + /** + * function from d3, get scaleRange of an ordinal scale + * @param {Array} domain ordinal scale's range + */ + function d3_scaleExtent(domain) { + var start = domain[0], stop = domain[domain.length - 1]; + return start < stop ? [start, stop] : [stop, start]; + } + + /** + * function from d3, get scaleRange of a scale + */ + function d3_scaleRange(scale) { + return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); + } + + /** + * function from d3, get subticks + * @param scale, scale + * @param ticks, major ticks of scale + * @param m, number of subdivide + */ + function d3_svg_axisSubdivide(scale, ticks, m) { + var subticks = []; + if (m && ticks.length > 1) { + var extent = d3_scaleExtent(scale.domain()), + i = -1, + n = ticks.length, + d = (ticks[1] - ticks[0]) / ++m, + j, + v; + while (++i < n) { + for (j = m; --j > 0;) { + if ((v = +ticks[i] - j * d) >= extent[0]) { + subticks.push(v); + } + } + } + for (--i, j = 0; ++j < m && (v = +ticks[i] + j * d) < extent[1];) { + subticks.push(v); + } + } + return subticks; + } + + var Axis = function () { + var scale = d3.scale.linear(), + orient = "bottom", + tickMajorSize = 6, + tickMinorSize = 6, + tickEndSize = 6, + tickPadding = 3, + tickArguments_ = [10], + tickFormat_, + tickSubdivide = 0, + + tickAttr_ = {}, + tickTextAttr_ = {}, + minorTickAttr_ = {}, + domainAttr_ = {}; + + /** + * @param paper: raphael's paper object. + * @return axisSet: raphael's set object. + */ + function axis(paper) { + // Ticks for quantitative scale, or domain values for ordinal scale. + var ticks = scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain(), + tickFormat = tickFormat_ === undefined ? + (scale.tickFormat ? + scale.tickFormat.apply(scale, tickArguments_) + : String) + : tickFormat_; + + var subticks = d3_svg_axisSubdivide(scale, ticks, tickSubdivide); + var range = d3_scaleRange(scale); + + var axisSet = paper.set(); + + switch (orient) { + case "bottom": + subticks.forEach(function (d, i, arr) { + var tickX = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2; + axisSet.push(paper + .path("M" + tickX + "," + tickMinorSize + "V0") + .attr(minorTickAttr_)); + }); + ticks.forEach(function (d, i, arr) { + var tickX = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2; + axisSet.push(paper + .path("M" + tickX + "," + tickMajorSize + "V0") + .attr(tickAttr_)); + axisSet.push(paper + .text(tickX, Math.max(tickMajorSize, 0) + tickPadding + 2, + typeof tickFormat === "function" ? tickFormat(d) : tickFormat) + .attr({"text-anchor": "middle"}) + .attr(tickTextAttr_)); + }); + axisSet.push(paper + .path("M" + range[0] + "," + tickEndSize + "V0H" + range[1] + "V" + tickEndSize) + .attr(domainAttr_)); + break; + + case "top": + subticks.forEach(function (d, i, arr) { + var tickX = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2; + axisSet.push(paper + .path("M" + tickX + "," + -tickMinorSize + "V0") + .attr(minorTickAttr_)); + }); + ticks.forEach(function (d, i, arr) { + var tickX = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2; + axisSet.push(paper + .path("M" + tickX + "," + -tickMajorSize + "V0") + .attr(tickAttr_)); + axisSet.push(paper + .text(tickX, -(Math.max(tickMajorSize, 0) + tickPadding + 2), + typeof tickFormat === "function" ? tickFormat(d) : tickFormat) + .attr({"text-anchor": "middle"}) + .attr(tickTextAttr_)); + }); + axisSet.push(paper + .path("M" + range[0] + "," + -tickEndSize + "V0H" + range[1] + "V" + -tickEndSize) + .attr(domainAttr_)); + break; + + case "left": + subticks.forEach(function (d, i, arr) { + var tickY = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2; + axisSet.push(paper + .path("M" + -tickMinorSize + "," + tickY + "H0") + .attr(minorTickAttr_)); + }); + ticks.forEach(function (d, i, arr) { + var tickY = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2; + axisSet.push(paper + .path("M" + -tickMajorSize + "," + tickY + "H0") + .attr(tickAttr_)); + axisSet.push(paper + .text(-(Math.max(tickMajorSize, 0) + tickPadding), tickY, + typeof tickFormat === "function" ? tickFormat(d) : tickFormat) + .attr({"text-anchor": "end"}) + .attr(tickTextAttr_)); + }); + axisSet.push(paper + .path("M" + -tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + -tickEndSize) + .attr(domainAttr_)); + break; + + case "right": + subticks.forEach(function (d, i, arr) { + var tickY = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2; + axisSet.push(paper + .path("M" + tickMinorSize + "," + tickY + "H0") + .attr(minorTickAttr_)); + }); + ticks.forEach(function (d, i, arr) { + var tickY = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2; + axisSet.push(paper + .path("M" + tickMajorSize + "," + tickY + "H0") + .attr(tickAttr_)); + axisSet.push(paper + .text(Math.max(tickMajorSize, 0) + tickPadding, tickY, + typeof tickFormat === "function" ? tickFormat(d) : tickFormat) + .attr({"text-anchor": "start"}) + .attr(tickTextAttr_)); + }); + axisSet.push(paper + .path("M" + tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + tickEndSize) + .attr(domainAttr_)); + break; + } + + return axisSet; + } + + /** + * get or set axis' scale. + */ + axis.scale = function (x) { + if (!arguments.length) { + return scale; + } + scale = x; + return axis; + }; + + /** + * get or set axis' orinet: "bottom", "top", "left", "right", default orient is bottom. + */ + axis.orient = function (x) { + if (!arguments.length) { + return orient; + } + orient = x; + return axis; + }; + + /** + * get or set axis' ticks number. + */ + axis.ticks = function () { + if (!arguments.length) { + return tickArguments_; + } + tickArguments_ = arguments; + return axis; + }; + + /** + * get or set axis' ticks format function, it's a function change format style. + * from one string format to another string format. + */ + axis.tickFormat = function (x) { + if (!arguments.length) { + return tickFormat_; + } + tickFormat_ = x; + return axis; + }; + + /** + * get or set axis' tick size(length of tick line, unit: px). + * @param arguments.length === 0, get axis' major tick size. + * @param arguments.length === 1, set axis' all tick sizes as x. + * @param arguments.length === 2, get axis' major tick size as x, minor and end size as y. + * @param arguments.length === 3, get axis' major tick size as x, minor size as y, end size as z. + */ + axis.tickSize = function (x, y, z) { + if (!arguments.length) { + return tickMajorSize; + } + var n = arguments.length - 1; + tickMajorSize = +x; + tickMinorSize = n > 1 ? +y : tickMajorSize; + tickEndSize = n > 0 ? +arguments[n] : tickMajorSize; + return axis; + }; + + /** + * get or set axis' tick padding(the distance between tick text and axis). + * @param x is a number, unit is px; + */ + axis.tickPadding = function (x) { + if (!arguments.length) { + return tickPadding; + } + tickPadding = +x; + return axis; + }; + + /** + * get or set axis' sub tick divide number(divide number between two major ticks). + */ + axis.tickSubdivide = function (x) { + if (!arguments.length) { + return tickSubdivide; + } + tickSubdivide = +x; + return axis; + }; + + /** + * get or set axis' tick attribute(Raphael format). + */ + axis.tickAttr = function (x) { + if (!arguments.length) { + return tickAttr_; + } + tickAttr_ = x; + return axis; + }; + + /** + * get or set axis' tick text attribute(Raphael format). + */ + axis.tickTextAttr = function (x) { + if (!arguments.length) { + return tickTextAttr_; + } + tickTextAttr_ = x; + return axis; + }; + + /** + * get or set axis' minor tick attribute(Raphael format). + */ + axis.minorTickAttr = function (x) { + if (!arguments.length) { + return minorTickAttr_; + } + minorTickAttr_ = x; + return axis; + }; + + /** + * get or set axis' domain(axis line) attribute(Raphael format). + */ + axis.domainAttr = function (x) { + if (!arguments.length) { + return domainAttr_; + } + domainAttr_ = x; + return axis; + }; + + return axis; + }; + + return Axis; +}); diff --git a/lib/charts/brush.js b/lib/charts/brush.js index 10083cc..7ba8ff8 100644 --- a/lib/charts/brush.js +++ b/lib/charts/brush.js @@ -1,615 +1,615 @@ -/*global d3,Raphael,$*/ -/*! - * Brush的兼容定义 - */ -;(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];}); - } -})('Brush', function (require) { - - var d3_svg_brush, - d3_svg_brushDispatch, - d3_svg_brushTarget, - d3_svg_brushX, - d3_svg_brushY, - d3_svg_brushExtent, - d3_svg_brushDrag, - d3_svg_brushResize, - d3_svg_brushCenter, - d3_svg_brushOffset, - d3_svg_brushEls; - - /** - * set foreground and resizers' x and width; - */ - function d3_svg_brushRedrawX(brushEls, extent) { - brushEls.fg.attr({"x": extent[0][0], - "width": extent[1][0] - extent[0][0] }); - brushEls.resizerSet.forEach(function (el) { - var orient = el.data("resizeOrient"); - - if (orient === "n" || - orient === "s" || - orient === "w" || - orient === "nw" || - orient === "sw") { - el.attr({"x": extent[0][0] - 2}); - } else { // "e" "ne" "se" - el.attr({"x": extent[1][0] - 2}); - } - if (orient === "n" || orient === "s") { - el.attr({"width": extent[1][0] - extent[0][0]}); - } - }); - } - - /** - * set foreground and resizers' y and height; - */ - function d3_svg_brushRedrawY(brushEls, extent) { - brushEls.fg.attr({"y": extent[0][1], - "height": extent[1][1] - extent[0][1] }); - brushEls.resizerSet.forEach(function (el) { - var orient = el.data("resizeOrient"); - if (orient === "n" || - orient === "e" || - orient === "w" || - orient === "nw" || - orient === "ne") { - el.attr({"y": extent[0][1] - 3}); - } else { // "s" "se" "sw" - el.attr({"y": extent[1][1] - 4}); - } - if (orient === "e" || orient === "w") { - el.attr({"height": extent[1][1] - extent[0][1]}); - } - }); - } - - /** - * function from d3, get scaleRange of an ordinal scale - * @param domain, ordinal scale's range - */ - function d3_scaleExtent(domain) { - var start = domain[0], stop = domain[domain.length - 1]; - return start < stop ? [start, stop] : [stop, start]; - } - - /** - * function from d3, get scaleRange of a scale - */ - function d3_scaleRange(scale) { - return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); - } - - /** - * function from d3, called by d3_svg_brushMove, compute new brush extent after brush moved - */ - function d3_svg_brushMove1(mouse, scale, i) { - var range = d3_scaleRange(scale), - r0 = range[0], - r1 = range[1], - offset = d3_svg_brushOffset[i], - size = d3_svg_brushExtent[1][i] - d3_svg_brushExtent[0][i], - min, - max; - - // When dragging, reduce the range by the extent size and offset. - if (d3_svg_brushDrag) { - r0 -= offset; - r1 -= size + offset; - } - - // Clamp the mouse so that the extent fits within the range extent. - min = Math.max(r0, Math.min(r1, mouse[i])); - - // Compute the new extent bounds. - if (d3_svg_brushDrag) { - max = (min += offset) + size; - } else { - // If the ALT key is pressed, then preserve the center of the extent. - if (d3_svg_brushCenter) { - offset = Math.max(r0, Math.min(r1, 2 * d3_svg_brushCenter[i] - min)); - } - - // Compute the min and max of the offset and mouse. - if (offset < min) { - max = min; - min = offset; - } else { - max = offset; - } - } - - // Update the stored bounds. - d3_svg_brushExtent[0][i] = min; - d3_svg_brushExtent[1][i] = max; - } - - /** - * function from d3, after brush moved, compute new brush extent - * and redraw foreground and resizer. - */ - function d3_svg_brushMove(e) { - if (d3_svg_brushOffset) { - var bgOffset = $(d3_svg_brushTarget).offset(); - var mouse = [e.pageX - bgOffset.left, e.pageY - bgOffset.top]; - - if (!d3_svg_brushDrag) { - // If needed, determine the center from the current extent. - if (e.altKey) { - if (!d3_svg_brushCenter) { - d3_svg_brushCenter = [ - (d3_svg_brushExtent[0][0] + d3_svg_brushExtent[1][0]) / 2, - (d3_svg_brushExtent[0][1] + d3_svg_brushExtent[1][1]) / 2 - ]; - } - - // Update the offset, for when the ALT key is released. - d3_svg_brushOffset[0] = d3_svg_brushExtent[+(mouse[0] < d3_svg_brushCenter[0])][0]; - d3_svg_brushOffset[1] = d3_svg_brushExtent[+(mouse[1] < d3_svg_brushCenter[1])][1]; - } else { - // When the ALT key is released, we clear the center. - d3_svg_brushCenter = null; - } - } - - // Update the brush extent for each dimension. - if (d3_svg_brushX) { - d3_svg_brushMove1(mouse, d3_svg_brushX, 0); - d3_svg_brushRedrawX(d3_svg_brushEls, d3_svg_brushExtent); - } - if (d3_svg_brushY) { - d3_svg_brushMove1(mouse, d3_svg_brushY, 1); - d3_svg_brushRedrawY(d3_svg_brushEls, d3_svg_brushExtent); - } - - // Notify listeners. - d3_svg_brushDispatch("brush"); - } - } - - /** - * function from d3, - * reset brush offset if user presses "space" key while brushing a new area, - * to ensure foreground's size unchanged while position changing. - */ - function d3_svg_brushKeydown(e) { - if (e.keyCode === 32 && d3_svg_brushTarget && !d3_svg_brushDrag) { - d3_svg_brushCenter = null; - d3_svg_brushOffset[0] -= d3_svg_brushExtent[1][0]; - d3_svg_brushOffset[1] -= d3_svg_brushExtent[1][1]; - d3_svg_brushDrag = 2; - e.stopPropagation(); - } - } - - /** - * function from d3, - * reset brush offset if "space" key up to restore normal drush state. - */ - function d3_svg_brushKeyup(e) { - if (e.keyCode === 32 && d3_svg_brushDrag === 2) { - d3_svg_brushOffset[0] += d3_svg_brushExtent[1][0]; - d3_svg_brushOffset[1] += d3_svg_brushExtent[1][1]; - d3_svg_brushDrag = 0; - e.stopPropagation(); - } - } - - /** - * function from d3, - * mouse up and stop brushing. - */ - function d3_svg_brushUp(e) { - if (d3_svg_brushOffset) { - d3_svg_brushMove(e); - d3_svg_brushEls.resizerSet.forEach(function (resizer) { - //adjust all resizers - var orient = resizer.data("resizeOrient"); - var size = d3_svg_brush.empty() ? 0 : 6; - if (orient === "n" || orient === "s") { - resizer.attr({"height": size}); - } else { - resizer.attr({"width": size}); - } - }); - d3_svg_brushDispatch("brushend"); - d3_svg_brush = - d3_svg_brushDispatch = - d3_svg_brushTarget = - d3_svg_brushX = - d3_svg_brushY = - d3_svg_brushExtent = - d3_svg_brushDrag = - d3_svg_brushResize = - d3_svg_brushCenter = - d3_svg_brushOffset = - d3_svg_brushEls = null; - e.stopPropagation(); - } - } - - var d3_svg_brushCursor = { - n: "ns-resize", - e: "ew-resize", - s: "ns-resize", - w: "ew-resize", - nw: "nwse-resize", - ne: "nesw-resize", - se: "nwse-resize", - sw: "nesw-resize" - }; - var vml_brushCursor = { - n: "row-resize", - e: "col-resize", - s: "row-resize", - w: "col-resize", - nw: "all-scroll", - ne: "all-scroll", - se: "all-scroll", - sw: "all-scroll" - }; - - var Brush = function () { - var event = d3.dispatch("brushstart", "brush", "brushend"), - x, // x-scale, optional - y, // y-scale, optional - extent = [[0, 0], [0, 0]], // [x0, y0], [x1, y1] - e, - left, - top, - width, - height, - backgroundAttr = { - "fill": "#dddddd", - "stroke": "none", - "cursor": "crosshair" - }, - foregroundAttr = { - "fill": "steelblue", - "stroke": "none", - "cursor": "move" - }, - brushStart = function () {}, - brushing = function () {}, - brushEnd = function () {}, - - brushEls = {}, - brushClass; - - /*! - * mouse down and start brushing or dragging. - */ - function down(e) { - var target = e.target, - bgOffset; - - // Store some global state for the duration of the brush gesture. - d3_svg_brush = brush; - d3_svg_brushTarget = $(brushEls.paper.canvas).parent(); - d3_svg_brushExtent = extent; - bgOffset = $(d3_svg_brushTarget).offset(); - - d3_svg_brushOffset = [e.pageX - bgOffset.left, e.pageY - bgOffset.top]; - d3_svg_brushEls = brushEls; - - // If the extent was clicked on, drag rather than brush; - // store the offset between the mouse and extent origin instead. - d3_svg_brushDrag = target.__brushNodeType__ === "fg" ? true : false; - if (d3_svg_brushDrag) { - d3_svg_brushOffset[0] = extent[0][0] - d3_svg_brushOffset[0]; - d3_svg_brushOffset[1] = extent[0][1] - d3_svg_brushOffset[1]; - } else if (/^resize/.test(target.__brushNodeType__)) { - // If a resizer was clicked on, record which side is to be resized. - // Also, set the offset to the opposite side. - d3_svg_brushResize = target.__brushNodeType__.split("_")[1]; - d3_svg_brushOffset[0] = extent[+(/w$/.test(d3_svg_brushResize))][0]; - d3_svg_brushOffset[1] = extent[+(/^n/.test(d3_svg_brushResize))][1]; - } else if (e.altKey) { - // If the ALT key is down when starting a brush, the center is at the mouse. - d3_svg_brushCenter = d3_svg_brushOffset.slice(); - } - - // Restrict which dimensions are resized. - d3_svg_brushX = !/^(n|s)$/.test(d3_svg_brushResize) && x; - d3_svg_brushY = !/^(e|w)$/.test(d3_svg_brushResize) && y; - - // Notify listeners. - d3_svg_brushDispatch = dispatcher(this, arguments); - d3_svg_brushDispatch("brushstart"); - d3_svg_brushMove(e); - e.stopPropagation(); - } - - /*! - * create brush - * input a Raphael paper, return a brush object. - */ - function brush(paper) { - var resizes = x && y ? ["n", "e", "s", "w", "nw", "ne", "se", "sw"] - : x ? ["e", "w"] - : y ? ["n", "s"] - : []; - - if (x) { - e = d3_scaleRange(x); - left = e[0]; - width = e[1] - e[0]; - } - - if (y) { - e = d3_scaleRange(y); - top = e[0]; - height = e[1] - e[0]; - } - - brushEls.paper = paper; - brushEls.brushSet = paper.set(); - brushEls.resizerSet = paper.set(); - brushEls.bg = paper.rect(left, top, width, height) - .attr({"fill": "#dddddd", - "stroke": "none", - "cursor": "crosshair" - }) - .attr(backgroundAttr); - brushEls.bg.node.__brushNodeType__ = "bg"; - brushEls.bg.node.ondragstart = function () { return false; };//firefox drag bug fix; - - brushClass = "brush" + brushEls.bg.id; - - //$(brushEls.bg.node).addClass("brush bg rvml"); // fail to svg - brushEls.bg.node.setAttribute("class", "brush bg rvml " + brushClass); - brushEls.bg.node.setAttribute("className", "brush bg rvml " + brushClass);// IE 6,7 - - brushEls.fg = paper.rect(left, top, (x ? 0 : width), (y ? 0 : height)) - .attr({"fill": "steelblue", - "stroke": "none", - "cursor": "move" - }) - .attr(foregroundAttr); - brushEls.fg.node.__brushNodeType__ = "fg"; - brushEls.fg.node.ondragstart = function () { return false; };//firefox drag bug fix; - //$(brushEls.fg.node).addClass("brush fg rvml"); //fail to svg - brushEls.fg.node.setAttribute("class", "brush fg rvml " + brushClass); - brushEls.fg.node.setAttribute("className", "brush fg rvml " + brushClass);// IE 6,7 - - resizes.forEach(function (d) { - var resizer = paper.rect(left, top, (x ? 6 : width), (y ? 6 : height)) - .data("resizeOrient", d) - .attr({"cursor": d3_svg_brushCursor[d], - "fill": "white", - "stroke": "black", - "opacity": 0}); - if (Raphael.vml) { - resizer.attr({"cursor": vml_brushCursor[d]}); - } - if (brush.empty()) { - //hide all resizers - if (d === "n" || d === "s") { - resizer.attr({"height": 0}); - } else { - resizer.attr({"width": 0}); - } - } - resizer.node.__brushNodeType__ = "resizer_" + d; - resizer.node.ondragstart = function () { return false; };//firefox drag bug fix; - //$(resizer.node).addClass("brush rvml " + d3_svg_brushCursor[d]); //fail to svg - resizer.node.setAttribute("class", "brush rvml " + brushClass + " " + d3_svg_brushCursor[d]); - //IE 6,7 - resizer.node.setAttribute("className", "brush rvml " + brushClass + " " + d3_svg_brushCursor[d]); - brushEls.resizerSet.push(resizer); - }); - - if (x) { - d3_svg_brushRedrawX(brushEls, extent); - } - - if (y) { - d3_svg_brushRedrawY(brushEls, extent); - } - - //$(paper.canvas).delegate(".brush","mousedown", down); - //$(paper.canvas).undelegate(".brush","mousedown", down); - //$(paper.canvas).delegate(".brush","mousedown", down); - //$(paper.canvas).off("mousedown", ".brush", down); - $(paper.canvas).on("mousedown", "." + brushClass, down); - - brush.brushElements = brushEls; - return brush; - } - - // dispatch event, bind data to golbal variant d3.event. - var dispatcher = function (that, argumentz) { - return function (type) { - var e = d3.event; - try { - d3.event = {type: type, target: brush}; - event[type].apply(that, argumentz); - } finally { - d3.event = e; - } - }; - }; - - /*! - * get or set brush's left - * @param z, a value in brush scale's domain - */ - brush.left = function (z) { - if (!arguments.length) { return left; } - left = z; - return brush; - }; - - /*! - * get or set brush's top - * @param z, a value in brush scale's domain - */ - brush.top = function (z) { - if (!arguments.length) { return top; } - top = z; - return brush; - }; - - /*! - * get or set brush's width - * @param z, a value in brush scale's domain - */ - brush.width = function (z) { - if (!arguments.length) { return width; } - width = z; - return brush; - }; - - /*! - * get or set brush's height - * @param z, a value in brush scale's domain - */ - brush.height = function (z) { - if (!arguments.length) { return height; } - height = z; - return brush; - }; - - /*! - * get or set brush's x scale - * @param z, d3's sacle object - */ - brush.x = function (z) { - if (!arguments.length) { return x; } - x = z; - return brush; - }; - - /*! - * get or set brush's y scale - * @param z, d3's sacle object - */ - brush.y = function (z) { - if (!arguments.length) { return y; } - y = z; - return brush; - }; - - /*! - * get or set brush's extent in scale's domain format. - * if both x and y exist, @param z's format is [[x0, y0], [x1, y1]] - * if only one of x and y exists, @param z's format is [x0, x1] or [y0, y1]. - */ - brush.extent = function (z) { - var x0, x1, y0, y1, t; - - // Invert the pixel extent to data-space. - if (!arguments.length) { - if (x) { - x0 = extent[0][0]; x1 = extent[1][0]; - if (x.invert) { - x0 = x.invert(x0); x1 = x.invert(x1); - } - if (x1 < x0) { - t = x0; x0 = x1; x1 = t; - } - } - if (y) { - y0 = extent[0][1]; y1 = extent[1][1]; - if (y.invert) { - y0 = y.invert(y0); y1 = y.invert(y1); - } - if (y1 < y0) { - t = y0; y0 = y1; y1 = t; - } - } - return x && y ? [[x0, y0], [x1, y1]] : x ? [x0, x1] : y && [y0, y1]; - } - - // Scale the data-space extent to pixels. - if (x) { - x0 = z[0]; x1 = z[1]; - if (y) { - x0 = x0[0]; x1 = x1[0]; - } - if (x.invert) { - x0 = x(x0); x1 = x(x1); - } - if (x1 < x0) { - t = x0; x0 = x1; x1 = t; - } - extent[0][0] = x0; extent[1][0] = x1; - } - if (y) { - y0 = z[0]; y1 = z[1]; - if (x) { - y0 = y0[1]; y1 = y1[1]; - } - if (y.invert) { - y0 = y(y0); y1 = y(y1); - } - if (y1 < y0) { - t = y0; y0 = y1; y1 = t; - } - extent[0][1] = y0; extent[1][1] = y1; - } - - return brush; - }; - - //empty extent and refresh foreground - brush.clear = function () { - extent[0][0] = extent[0][1] = extent[1][0] = extent[1][1] = 0; - brush.refresh(); - return brush; - }; - - //refresh foreground - brush.refresh = function () { - if (x) { - d3_svg_brushRedrawX(brushEls, extent); - } - if (y) { - d3_svg_brushRedrawY(brushEls, extent); - } - return brush; - }; - - //remove all brush elements, so users can reset brush attributes and redraw it. - brush.remove = function () { - $(paper.canvas).off("mousedown", "." + brushClass, down); - brushEls.fg.remove(); - brushEls.bg.remove(); - brushEls.resizerSet.remove(); - return brush; - }; - - // if brush is empty, return true, else false; - brush.empty = function () { - return (x && extent[0][0] === extent[1][0]) || (y && extent[0][1] === extent[1][1]); - }; - - // set background attribute. - brush.backgroundAttr = function (x) { - if (!arguments.length) { return backgroundAttr; } - backgroundAttr = x; - return brush; - }; - - // set foreground attribute. - brush.foregroundAttr = function (x) { - if (!arguments.length) { return foregroundAttr; } - foregroundAttr = x; - return brush; - }; - - $(document).bind("mousemove", d3_svg_brushMove) - .bind("mouseup", d3_svg_brushUp) - .bind("keydown", d3_svg_brushKeydown) - .bind("keyup", d3_svg_brushKeyup); - - return d3.rebind(brush, event, "on"); - }; - - return Brush; -}); +/*global d3,Raphael,$*/ +/*! + * Brush的兼容定义 + */ +;(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];}); + } +})('Brush', function (require) { + + var d3_svg_brush, + d3_svg_brushDispatch, + d3_svg_brushTarget, + d3_svg_brushX, + d3_svg_brushY, + d3_svg_brushExtent, + d3_svg_brushDrag, + d3_svg_brushResize, + d3_svg_brushCenter, + d3_svg_brushOffset, + d3_svg_brushEls; + + /** + * set foreground and resizers' x and width; + */ + function d3_svg_brushRedrawX(brushEls, extent) { + brushEls.fg.attr({"x": extent[0][0], + "width": extent[1][0] - extent[0][0] }); + brushEls.resizerSet.forEach(function (el) { + var orient = el.data("resizeOrient"); + + if (orient === "n" || + orient === "s" || + orient === "w" || + orient === "nw" || + orient === "sw") { + el.attr({"x": extent[0][0] - 2}); + } else { // "e" "ne" "se" + el.attr({"x": extent[1][0] - 2}); + } + if (orient === "n" || orient === "s") { + el.attr({"width": extent[1][0] - extent[0][0]}); + } + }); + } + + /** + * set foreground and resizers' y and height; + */ + function d3_svg_brushRedrawY(brushEls, extent) { + brushEls.fg.attr({"y": extent[0][1], + "height": extent[1][1] - extent[0][1] }); + brushEls.resizerSet.forEach(function (el) { + var orient = el.data("resizeOrient"); + if (orient === "n" || + orient === "e" || + orient === "w" || + orient === "nw" || + orient === "ne") { + el.attr({"y": extent[0][1] - 3}); + } else { // "s" "se" "sw" + el.attr({"y": extent[1][1] - 4}); + } + if (orient === "e" || orient === "w") { + el.attr({"height": extent[1][1] - extent[0][1]}); + } + }); + } + + /** + * function from d3, get scaleRange of an ordinal scale + * @param domain, ordinal scale's range + */ + function d3_scaleExtent(domain) { + var start = domain[0], stop = domain[domain.length - 1]; + return start < stop ? [start, stop] : [stop, start]; + } + + /** + * function from d3, get scaleRange of a scale + */ + function d3_scaleRange(scale) { + return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); + } + + /** + * function from d3, called by d3_svg_brushMove, compute new brush extent after brush moved + */ + function d3_svg_brushMove1(mouse, scale, i) { + var range = d3_scaleRange(scale), + r0 = range[0], + r1 = range[1], + offset = d3_svg_brushOffset[i], + size = d3_svg_brushExtent[1][i] - d3_svg_brushExtent[0][i], + min, + max; + + // When dragging, reduce the range by the extent size and offset. + if (d3_svg_brushDrag) { + r0 -= offset; + r1 -= size + offset; + } + + // Clamp the mouse so that the extent fits within the range extent. + min = Math.max(r0, Math.min(r1, mouse[i])); + + // Compute the new extent bounds. + if (d3_svg_brushDrag) { + max = (min += offset) + size; + } else { + // If the ALT key is pressed, then preserve the center of the extent. + if (d3_svg_brushCenter) { + offset = Math.max(r0, Math.min(r1, 2 * d3_svg_brushCenter[i] - min)); + } + + // Compute the min and max of the offset and mouse. + if (offset < min) { + max = min; + min = offset; + } else { + max = offset; + } + } + + // Update the stored bounds. + d3_svg_brushExtent[0][i] = min; + d3_svg_brushExtent[1][i] = max; + } + + /** + * function from d3, after brush moved, compute new brush extent + * and redraw foreground and resizer. + */ + function d3_svg_brushMove(e) { + if (d3_svg_brushOffset) { + var bgOffset = $(d3_svg_brushTarget).offset(); + var mouse = [e.pageX - bgOffset.left, e.pageY - bgOffset.top]; + + if (!d3_svg_brushDrag) { + // If needed, determine the center from the current extent. + if (e.altKey) { + if (!d3_svg_brushCenter) { + d3_svg_brushCenter = [ + (d3_svg_brushExtent[0][0] + d3_svg_brushExtent[1][0]) / 2, + (d3_svg_brushExtent[0][1] + d3_svg_brushExtent[1][1]) / 2 + ]; + } + + // Update the offset, for when the ALT key is released. + d3_svg_brushOffset[0] = d3_svg_brushExtent[+(mouse[0] < d3_svg_brushCenter[0])][0]; + d3_svg_brushOffset[1] = d3_svg_brushExtent[+(mouse[1] < d3_svg_brushCenter[1])][1]; + } else { + // When the ALT key is released, we clear the center. + d3_svg_brushCenter = null; + } + } + + // Update the brush extent for each dimension. + if (d3_svg_brushX) { + d3_svg_brushMove1(mouse, d3_svg_brushX, 0); + d3_svg_brushRedrawX(d3_svg_brushEls, d3_svg_brushExtent); + } + if (d3_svg_brushY) { + d3_svg_brushMove1(mouse, d3_svg_brushY, 1); + d3_svg_brushRedrawY(d3_svg_brushEls, d3_svg_brushExtent); + } + + // Notify listeners. + d3_svg_brushDispatch("brush"); + } + } + + /** + * function from d3, + * reset brush offset if user presses "space" key while brushing a new area, + * to ensure foreground's size unchanged while position changing. + */ + function d3_svg_brushKeydown(e) { + if (e.keyCode === 32 && d3_svg_brushTarget && !d3_svg_brushDrag) { + d3_svg_brushCenter = null; + d3_svg_brushOffset[0] -= d3_svg_brushExtent[1][0]; + d3_svg_brushOffset[1] -= d3_svg_brushExtent[1][1]; + d3_svg_brushDrag = 2; + e.stopPropagation(); + } + } + + /** + * function from d3, + * reset brush offset if "space" key up to restore normal drush state. + */ + function d3_svg_brushKeyup(e) { + if (e.keyCode === 32 && d3_svg_brushDrag === 2) { + d3_svg_brushOffset[0] += d3_svg_brushExtent[1][0]; + d3_svg_brushOffset[1] += d3_svg_brushExtent[1][1]; + d3_svg_brushDrag = 0; + e.stopPropagation(); + } + } + + /** + * function from d3, + * mouse up and stop brushing. + */ + function d3_svg_brushUp(e) { + if (d3_svg_brushOffset) { + d3_svg_brushMove(e); + d3_svg_brushEls.resizerSet.forEach(function (resizer) { + //adjust all resizers + var orient = resizer.data("resizeOrient"); + var size = d3_svg_brush.empty() ? 0 : 6; + if (orient === "n" || orient === "s") { + resizer.attr({"height": size}); + } else { + resizer.attr({"width": size}); + } + }); + d3_svg_brushDispatch("brushend"); + d3_svg_brush = + d3_svg_brushDispatch = + d3_svg_brushTarget = + d3_svg_brushX = + d3_svg_brushY = + d3_svg_brushExtent = + d3_svg_brushDrag = + d3_svg_brushResize = + d3_svg_brushCenter = + d3_svg_brushOffset = + d3_svg_brushEls = null; + e.stopPropagation(); + } + } + + var d3_svg_brushCursor = { + n: "ns-resize", + e: "ew-resize", + s: "ns-resize", + w: "ew-resize", + nw: "nwse-resize", + ne: "nesw-resize", + se: "nwse-resize", + sw: "nesw-resize" + }; + var vml_brushCursor = { + n: "row-resize", + e: "col-resize", + s: "row-resize", + w: "col-resize", + nw: "all-scroll", + ne: "all-scroll", + se: "all-scroll", + sw: "all-scroll" + }; + + var Brush = function () { + var event = d3.dispatch("brushstart", "brush", "brushend"), + x, // x-scale, optional + y, // y-scale, optional + extent = [[0, 0], [0, 0]], // [x0, y0], [x1, y1] + e, + left, + top, + width, + height, + backgroundAttr = { + "fill": "#dddddd", + "stroke": "none", + "cursor": "crosshair" + }, + foregroundAttr = { + "fill": "steelblue", + "stroke": "none", + "cursor": "move" + }, + brushStart = function () {}, + brushing = function () {}, + brushEnd = function () {}, + + brushEls = {}, + brushClass; + + /*! + * mouse down and start brushing or dragging. + */ + function down(e) { + var target = e.target, + bgOffset; + + // Store some global state for the duration of the brush gesture. + d3_svg_brush = brush; + d3_svg_brushTarget = $(brushEls.paper.canvas).parent(); + d3_svg_brushExtent = extent; + bgOffset = $(d3_svg_brushTarget).offset(); + + d3_svg_brushOffset = [e.pageX - bgOffset.left, e.pageY - bgOffset.top]; + d3_svg_brushEls = brushEls; + + // If the extent was clicked on, drag rather than brush; + // store the offset between the mouse and extent origin instead. + d3_svg_brushDrag = target.__brushNodeType__ === "fg" ? true : false; + if (d3_svg_brushDrag) { + d3_svg_brushOffset[0] = extent[0][0] - d3_svg_brushOffset[0]; + d3_svg_brushOffset[1] = extent[0][1] - d3_svg_brushOffset[1]; + } else if (/^resize/.test(target.__brushNodeType__)) { + // If a resizer was clicked on, record which side is to be resized. + // Also, set the offset to the opposite side. + d3_svg_brushResize = target.__brushNodeType__.split("_")[1]; + d3_svg_brushOffset[0] = extent[+(/w$/.test(d3_svg_brushResize))][0]; + d3_svg_brushOffset[1] = extent[+(/^n/.test(d3_svg_brushResize))][1]; + } else if (e.altKey) { + // If the ALT key is down when starting a brush, the center is at the mouse. + d3_svg_brushCenter = d3_svg_brushOffset.slice(); + } + + // Restrict which dimensions are resized. + d3_svg_brushX = !/^(n|s)$/.test(d3_svg_brushResize) && x; + d3_svg_brushY = !/^(e|w)$/.test(d3_svg_brushResize) && y; + + // Notify listeners. + d3_svg_brushDispatch = dispatcher(this, arguments); + d3_svg_brushDispatch("brushstart"); + d3_svg_brushMove(e); + e.stopPropagation(); + } + + /*! + * create brush + * input a Raphael paper, return a brush object. + */ + function brush(paper) { + var resizes = x && y ? ["n", "e", "s", "w", "nw", "ne", "se", "sw"] + : x ? ["e", "w"] + : y ? ["n", "s"] + : []; + + if (x) { + e = d3_scaleRange(x); + left = e[0]; + width = e[1] - e[0]; + } + + if (y) { + e = d3_scaleRange(y); + top = e[0]; + height = e[1] - e[0]; + } + + brushEls.paper = paper; + brushEls.brushSet = paper.set(); + brushEls.resizerSet = paper.set(); + brushEls.bg = paper.rect(left, top, width, height) + .attr({"fill": "#dddddd", + "stroke": "none", + "cursor": "crosshair" + }) + .attr(backgroundAttr); + brushEls.bg.node.__brushNodeType__ = "bg"; + brushEls.bg.node.ondragstart = function () { return false; };//firefox drag bug fix; + + brushClass = "brush" + brushEls.bg.id; + + //$(brushEls.bg.node).addClass("brush bg rvml"); // fail to svg + brushEls.bg.node.setAttribute("class", "brush bg rvml " + brushClass); + brushEls.bg.node.setAttribute("className", "brush bg rvml " + brushClass);// IE 6,7 + + brushEls.fg = paper.rect(left, top, (x ? 0 : width), (y ? 0 : height)) + .attr({"fill": "steelblue", + "stroke": "none", + "cursor": "move" + }) + .attr(foregroundAttr); + brushEls.fg.node.__brushNodeType__ = "fg"; + brushEls.fg.node.ondragstart = function () { return false; };//firefox drag bug fix; + //$(brushEls.fg.node).addClass("brush fg rvml"); //fail to svg + brushEls.fg.node.setAttribute("class", "brush fg rvml " + brushClass); + brushEls.fg.node.setAttribute("className", "brush fg rvml " + brushClass);// IE 6,7 + + resizes.forEach(function (d) { + var resizer = paper.rect(left, top, (x ? 6 : width), (y ? 6 : height)) + .data("resizeOrient", d) + .attr({"cursor": d3_svg_brushCursor[d], + "fill": "white", + "stroke": "black", + "opacity": 0}); + if (Raphael.vml) { + resizer.attr({"cursor": vml_brushCursor[d]}); + } + if (brush.empty()) { + //hide all resizers + if (d === "n" || d === "s") { + resizer.attr({"height": 0}); + } else { + resizer.attr({"width": 0}); + } + } + resizer.node.__brushNodeType__ = "resizer_" + d; + resizer.node.ondragstart = function () { return false; };//firefox drag bug fix; + //$(resizer.node).addClass("brush rvml " + d3_svg_brushCursor[d]); //fail to svg + resizer.node.setAttribute("class", "brush rvml " + brushClass + " " + d3_svg_brushCursor[d]); + //IE 6,7 + resizer.node.setAttribute("className", "brush rvml " + brushClass + " " + d3_svg_brushCursor[d]); + brushEls.resizerSet.push(resizer); + }); + + if (x) { + d3_svg_brushRedrawX(brushEls, extent); + } + + if (y) { + d3_svg_brushRedrawY(brushEls, extent); + } + + //$(paper.canvas).delegate(".brush","mousedown", down); + //$(paper.canvas).undelegate(".brush","mousedown", down); + //$(paper.canvas).delegate(".brush","mousedown", down); + //$(paper.canvas).off("mousedown", ".brush", down); + $(paper.canvas).on("mousedown", "." + brushClass, down); + + brush.brushElements = brushEls; + return brush; + } + + // dispatch event, bind data to golbal variant d3.event. + var dispatcher = function (that, argumentz) { + return function (type) { + var e = d3.event; + try { + d3.event = {type: type, target: brush}; + event[type].apply(that, argumentz); + } finally { + d3.event = e; + } + }; + }; + + /*! + * get or set brush's left + * @param z, a value in brush scale's domain + */ + brush.left = function (z) { + if (!arguments.length) { return left; } + left = z; + return brush; + }; + + /*! + * get or set brush's top + * @param z, a value in brush scale's domain + */ + brush.top = function (z) { + if (!arguments.length) { return top; } + top = z; + return brush; + }; + + /*! + * get or set brush's width + * @param z, a value in brush scale's domain + */ + brush.width = function (z) { + if (!arguments.length) { return width; } + width = z; + return brush; + }; + + /*! + * get or set brush's height + * @param z, a value in brush scale's domain + */ + brush.height = function (z) { + if (!arguments.length) { return height; } + height = z; + return brush; + }; + + /*! + * get or set brush's x scale + * @param z, d3's sacle object + */ + brush.x = function (z) { + if (!arguments.length) { return x; } + x = z; + return brush; + }; + + /*! + * get or set brush's y scale + * @param z, d3's sacle object + */ + brush.y = function (z) { + if (!arguments.length) { return y; } + y = z; + return brush; + }; + + /*! + * get or set brush's extent in scale's domain format. + * if both x and y exist, @param z's format is [[x0, y0], [x1, y1]] + * if only one of x and y exists, @param z's format is [x0, x1] or [y0, y1]. + */ + brush.extent = function (z) { + var x0, x1, y0, y1, t; + + // Invert the pixel extent to data-space. + if (!arguments.length) { + if (x) { + x0 = extent[0][0]; x1 = extent[1][0]; + if (x.invert) { + x0 = x.invert(x0); x1 = x.invert(x1); + } + if (x1 < x0) { + t = x0; x0 = x1; x1 = t; + } + } + if (y) { + y0 = extent[0][1]; y1 = extent[1][1]; + if (y.invert) { + y0 = y.invert(y0); y1 = y.invert(y1); + } + if (y1 < y0) { + t = y0; y0 = y1; y1 = t; + } + } + return x && y ? [[x0, y0], [x1, y1]] : x ? [x0, x1] : y && [y0, y1]; + } + + // Scale the data-space extent to pixels. + if (x) { + x0 = z[0]; x1 = z[1]; + if (y) { + x0 = x0[0]; x1 = x1[0]; + } + if (x.invert) { + x0 = x(x0); x1 = x(x1); + } + if (x1 < x0) { + t = x0; x0 = x1; x1 = t; + } + extent[0][0] = x0; extent[1][0] = x1; + } + if (y) { + y0 = z[0]; y1 = z[1]; + if (x) { + y0 = y0[1]; y1 = y1[1]; + } + if (y.invert) { + y0 = y(y0); y1 = y(y1); + } + if (y1 < y0) { + t = y0; y0 = y1; y1 = t; + } + extent[0][1] = y0; extent[1][1] = y1; + } + + return brush; + }; + + //empty extent and refresh foreground + brush.clear = function () { + extent[0][0] = extent[0][1] = extent[1][0] = extent[1][1] = 0; + brush.refresh(); + return brush; + }; + + //refresh foreground + brush.refresh = function () { + if (x) { + d3_svg_brushRedrawX(brushEls, extent); + } + if (y) { + d3_svg_brushRedrawY(brushEls, extent); + } + return brush; + }; + + //remove all brush elements, so users can reset brush attributes and redraw it. + brush.remove = function () { + $(paper.canvas).off("mousedown", "." + brushClass, down); + brushEls.fg.remove(); + brushEls.bg.remove(); + brushEls.resizerSet.remove(); + return brush; + }; + + // if brush is empty, return true, else false; + brush.empty = function () { + return (x && extent[0][0] === extent[1][0]) || (y && extent[0][1] === extent[1][1]); + }; + + // set background attribute. + brush.backgroundAttr = function (x) { + if (!arguments.length) { return backgroundAttr; } + backgroundAttr = x; + return brush; + }; + + // set foreground attribute. + brush.foregroundAttr = function (x) { + if (!arguments.length) { return foregroundAttr; } + foregroundAttr = x; + return brush; + }; + + $(document).bind("mousemove", d3_svg_brushMove) + .bind("mouseup", d3_svg_brushUp) + .bind("keydown", d3_svg_brushKeydown) + .bind("keyup", d3_svg_brushKeyup); + + return d3.rebind(brush, event, "on"); + }; + + return Brush; +}); diff --git a/lib/charts/bullet.js b/lib/charts/bullet.js index b363c75..3473ea3 100644 --- a/lib/charts/bullet.js +++ b/lib/charts/bullet.js @@ -1,349 +1,349 @@ -/*global Raphael, d3 */ -/*! - * Bullet的兼容性定义 - */ -;(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];}); - } -})('Bullet', function (require) { - var DataV = require('DataV'); - var Axis = require('Axis'); - - /** - * Bullet构造函数 - * Options: - * - * - `width` 数字,图片宽度,默认为200,表示图片高200px - * - `height` 数字,图片高度,默认为80 - * - `orient` string,图片方向,默认为"horizonal",表示水平方向。若为"vertical",则表示垂直方向 - * - `axisStyle` string, 坐标系类型,默认为"linear",表示坐标系为线性坐标系。若为"log",则表示对数坐标系 - * - `logBase` 数字, 采用对数坐标系时的对数基,默认为Math.E - * - `tickDivide` 数字,表示坐标的标尺的分段数,默认为5 - * - `margin` 数字数组,表示图片上、右、下、左的边距,默认为 [20, 20, 20, 20] - * - `centerBarRatio` 数字,表示中间的测度条的高度与背景条高度的比值, 默认为0.3 - * - `markerWidth` 数字,表示标记条的宽度, 默认为4,单位为像素 - * - `markerRatio` 数字,表示标记条的高度与背景条高度的比值,默认为0.7 - * - `titleRatio` 数字,表示子弹图title的高度与背景条高度的比值,默认为0.6,此时title与subtitle的比值 - * - `backgroundColor` string数组,表示背景条颜色的渐变数组,默认为["#666", "#ddd"],背景条颜色就是这两种颜色的渐变 - * - `measureColor` string数组,表示测度条颜色的渐变数组,默认为["steelblue", "#B0C4DE"],测度条颜色就是这两种颜色的渐变 - * - `markerColor` string,表示标记条的颜色,默认为"#000" - * - * Examples: - * ``` - * //Create bullet in a dom node with id "chart", width is 500; height is 600px; - * var bullet = new Bullet("chart", { - * "width": 500, - * "height": 600, - * "margin": [10, 10, 20, 70] - * }); - * //Create bullet with log base; - * var log = new Bullet("chart2", { - * width: 300, - * height: 60, - * margin: [10, 10, 20, 70], - * backgroundColor: ["#66f", "#ddf"], - * measureColor: ["#000", "#000"], - * markerColor: "#44f", - * axisStyle: "log", - * logBase: 10 - * }); - * ``` - * @param {Mix} node The dom node or dom node Id - * @param {Object} options options json object for determin stream style. - */ - var Bullet = DataV.extend(DataV.Chart, { - initialize: function (node, options) { - this.type = "Bullet"; - this.node = this.checkContainer(node); - // Properties - this.defaults.orient = "horizonal"; // "horizonal", "vertical" - this.defaults.axisStyle = "linear"; // "linear", "log" - this.defaults.logBase = Math.E; - this.defaults.tickDivide = 5; - this.defaults.margin = [10, 10, 20, 80];//top, right, bottom, left - this.defaults.centerBarRatio = 0.3; - this.defaults.markerWidth = 4; - this.defaults.markerRatio = 0.7; - this.defaults.titleRatio = 0.6; //title's text height : subtitle's text height = 6:4 - this.defaults.backgroundColor = ["#666", "#ddd"]; //dark, light - this.defaults.measureColor = ["steelblue", "#B0C4DE"]; //dark, light - this.defaults.markerColor = "#000"; - - // canvas - this.defaults.width = 200; - this.defaults.height = 80; - - this.setOptions(options); - this.createCanvas(); - } - }); - - /*! - * 创建画布 - */ - Bullet.prototype.createCanvas = function () { - var conf = this.defaults; - this.canvas = new Raphael(this.node, conf.width, conf.height); - }; - - /** - * 设置数据源 - * Examples: - * ``` - * bullet.setSource({ - * title: "Sample", - * subtitle: "ratio", - * ranges: [0, 0.5, 0.8, 1], - * measures: [0.7, 0.9], - * markers: [0.6], - * rangeTitles: ["below 50%", "top 20% - 50%", "top 20%"], - * measureTitles: ["value is 0.7", "value is 0.9"], - * markerTitles: ["mean is 0.6"] - * }) - * ``` - * @param {Object} source 数据源 - */ - Bullet.prototype.setSource = function (source) { - var conf = this.defaults, - range, - axisOrient; - this.data = source; - if (conf.orient === "horizonal") { - axisOrient = "bottom"; - range = [conf.margin[3], conf.width - conf.margin[1]]; - } else if (conf.orient === "vertical") { - axisOrient = "left"; - range = [conf.height - conf.margin[2], conf.margin[0]]; - } - - if (conf.axisStyle === "linear") { - this.scale = d3.scale.linear(); - } else if (conf.axisStyle === "log") { - this.scale = d3.scale.log(); - } - - this.data.min = this.data.ranges[0]; - this.data.max = this.data.ranges[this.data.ranges.length - 1]; - this.scale.domain([this.data.min, this.data.max]) - .range(range); - - if (conf.axisStyle === "linear") { - this.axis = Axis().scale(this.scale).orient(axisOrient).ticks(conf.tickDivide).domainAttr({"stroke": "none"}); - } else if (conf.axisStyle === "log") { - this.logScale = d3.scale.linear() - .domain([Math.log(this.data.min)/Math.log(conf.logBase), Math.log(this.data.max)/Math.log(conf.logBase)]) - .range(range); - this.axis = Axis() - .orient(axisOrient) - .scale(this.logScale) - .ticks(conf.tickDivide) - .tickFormat(function (d) {return Math.round(Math.pow(conf.logBase, d));}) - .domainAttr({"stroke": "none"}); - } - }; - - /*! - * generate bullet dom path - */ - Bullet.prototype.generatePaths = function () { - var conf = this.defaults; - //get color function - if (conf.backgroundColor) { - this.color = d3.interpolateRgb.apply(null, [conf.backgroundColor[0], conf.backgroundColor[1]]); - } - if (conf.measureColor) { - this.measureColor = d3.interpolateRgb.apply(null, [conf.measureColor[0], conf.measureColor[1]]); - } - - if (conf.orient === "horizonal") { - this.paintHorizonal(); - } else if (conf.orient === "vertical") { - this.paintVertical(); - } - }; - - /*! - * paint orient horizonal bullet - */ - Bullet.prototype.paintHorizonal = function () { - var conf = this.defaults; - var paper = this.canvas, - data = this.data, - m = conf.margin, - ranges = [], - measures = [], - markers = [], - rangeTitles = [], - i, - l, - rect, - titleRatio, - w, - h = conf.height - m[0] - m[2], - left; - - //axis - this.axis(paper).attr({transform: "t" + 0 + ',' + (conf.height - m[2])}); - //color rect - ranges = data.ranges; - if (data.rangeTitles) { - rangeTitles = data.rangeTitles; - } - left = m[3]; - for (i = 0, l = ranges.length - 1; i < l; i++) { - w = this.scale(ranges[i + 1]) - this.scale(ranges[i]); - rect = paper.rect(left, m[0], w, h) - .attr({"stroke": "none", - "fill": this.color(l === 1 ? 1 : i / (l - 1)), - "title": rangeTitles[i] ? rangeTitles[i] : ""}); - left += w; - } - - //measure bar - data.measures.forEach(function (d, i) { - var mTitles = data.measureTitles; - var mTitle = mTitles && mTitles[i] ? mTitles[i] : ""; - measures.push({measure: d, measureTitle: mTitle}); - }); - measures.sort(function (a, b) { return d3.ascending(a.measure, b.measure); }); - left = this.scale(data.min); - for (i = 0, l = measures.length; i < l; i++) { - value = Math.max(data.min, Math.min(data.max, measures[i].measure)); - w = this.scale(value) - left; - paper.rect(left, - m[0] + h * (1 - conf.centerBarRatio) / 2, - w, - h * conf.centerBarRatio) - .attr({"stroke": "none", "fill": this.measureColor(l === 1 ? 1 : i / (l - 1)), "title": measures[i].measureTitle}); - left += w; - } - - //marker bar - markers = data.markers; - for (i = 0, l = markers.length; i < l; i++) { - paper.rect(this.scale(markers[i]) - conf.markerWidth / 2, - m[0] + h * (1 - conf.markerRatio) / 2, - conf.markerWidth, - h * conf.markerRatio) - .attr({"stroke": "none", "fill": conf.markerColor, - "title": data.markerTitles && data.markerTitles[i] ? data.markerTitles[i] : ""}); - } - - //title - if (data.title) { - titleRatio = data.subtitle ? conf.titleRatio : 1; - this.title = paper.text(m[3] - 5, m[0] + h / 2, data.title) - .attr({"text-anchor": "end", "font-weight": "bold", "font-size": h * titleRatio * 0.9}); - } - - //subtitle - if (data.subtitle) { - this.subtitle = paper.text(m[3] - 5, conf.height - m[2], data.subtitle) - .attr({"text-anchor": "end", "font-size": h * (1 - conf.titleRatio) * 0.9}); - } - }; - - /*! - * paint orient vertical bullet - */ - Bullet.prototype.paintVertical = function () { - var conf = this.defaults; - var paper = this.canvas, - data = this.data, - m = conf.margin, - ranges = [], - measures = [], - markers = [], - rangeTitles = [], - i, - l, - rect, - titleRatio, - w = conf.width - m[1] - m[3], - h, - bottom; - - //axis - this.axis(paper).attr({transform: "t" + m[3] + ',' + 0}); - - //color rect - ranges = data.ranges; - if (data.rangeTitles) { - rangeTitles = data.rangeTitles; - } - bottom = conf.height - m[2]; - for (i = 0, l = ranges.length - 1; i < l; i++) { - h = -this.scale(ranges[i + 1]) + this.scale(ranges[i]); - rect = paper.rect(m[3], bottom - h, w, h) - .attr({"stroke": "none", - "fill": this.color(l === 1 ? 1 : i / (l - 1)), - "title": rangeTitles[i] ? rangeTitles[i] : ""}); - bottom -= h; - } - - //measure bar - data.measures.forEach(function (d, i) { - var mTitles = data.measureTitles; - var mTitle = mTitles && mTitles[i] ? mTitles[i] : ""; - measures.push({measure: d, measureTitle: mTitle}); - }); - measures.sort(function (a, b) { return d3.ascending(a.measure, b.measure); }); - bottom = this.scale(data.min); - for (i = 0, l = measures.length; i < l; i++) { - value = Math.max(data.min, Math.min(data.max, measures[i].measure)); - h = -this.scale(value) + bottom; - paper.rect(m[3] + w * (1 - conf.centerBarRatio) / 2, - bottom - h, - w * conf.centerBarRatio, - h) - .attr({"stroke": "none", "fill": this.measureColor(l === 1 ? 1 : i / (l - 1)), "title": measures[i].measureTitle}); - bottom -= h; - } - - //marker bar - markers = data.markers; - for (i = 0, l = markers.length; i < l; i++) { - paper.rect(m[3] + w * (1 - conf.markerRatio) / 2, - this.scale(markers[i]) - conf.markerWidth / 2, - w * conf.markerRatio, - conf.markerWidth) - .attr({"stroke": "none", "fill": conf.markerColor, - "title": data.markerTitles && data.markerTitles[i] ? data.markerTitles[i] : ""}); - } - - //title - if (data.title) { - titleRatio = data.subtitle ? conf.titleRatio : 1; - m[0] *= 0.9; //some ratio adjust; - this.title = paper.text((conf.width + m[3] - m[1])/ 2, m[0] * titleRatio / 2, data.title) - .attr({"text-anchor": "middle", "font-weight": "bold", "font-size": m[0] * titleRatio * 0.8}); - } - - //subtitle - if (data.subtitle) { - this.subtitle = paper.text((conf.width + m[3] - m[1])/ 2, m[0] * (1 - (1 - titleRatio) / 2), data.subtitle) - .attr({"text-anchor": "middle", "font-size": m[0] * (1 - titleRatio) * 0.8}); - } - }; - - /*! - * clean canvas - */ - Bullet.prototype.clearCanvas = function () { - this.canvas.clear(); - }; - - /** - * render bullet - */ - Bullet.prototype.render = function (options) { - this.setOptions(options); - this.generatePaths(); - }; - - return Bullet; -}); - +/*global Raphael, d3 */ +/*! + * Bullet的兼容性定义 + */ +;(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];}); + } +})('Bullet', function (require) { + var DataV = require('DataV'); + var Axis = require('Axis'); + + /** + * Bullet构造函数 + * Options: + * + * - `width` 数字,图片宽度,默认为200,表示图片高200px + * - `height` 数字,图片高度,默认为80 + * - `orient` string,图片方向,默认为"horizonal",表示水平方向。若为"vertical",则表示垂直方向 + * - `axisStyle` string, 坐标系类型,默认为"linear",表示坐标系为线性坐标系。若为"log",则表示对数坐标系 + * - `logBase` 数字, 采用对数坐标系时的对数基,默认为Math.E + * - `tickDivide` 数字,表示坐标的标尺的分段数,默认为5 + * - `margin` 数字数组,表示图片上、右、下、左的边距,默认为 [20, 20, 20, 20] + * - `centerBarRatio` 数字,表示中间的测度条的高度与背景条高度的比值, 默认为0.3 + * - `markerWidth` 数字,表示标记条的宽度, 默认为4,单位为像素 + * - `markerRatio` 数字,表示标记条的高度与背景条高度的比值,默认为0.7 + * - `titleRatio` 数字,表示子弹图title的高度与背景条高度的比值,默认为0.6,此时title与subtitle的比值 + * - `backgroundColor` string数组,表示背景条颜色的渐变数组,默认为["#666", "#ddd"],背景条颜色就是这两种颜色的渐变 + * - `measureColor` string数组,表示测度条颜色的渐变数组,默认为["steelblue", "#B0C4DE"],测度条颜色就是这两种颜色的渐变 + * - `markerColor` string,表示标记条的颜色,默认为"#000" + * + * Examples: + * ``` + * //Create bullet in a dom node with id "chart", width is 500; height is 600px; + * var bullet = new Bullet("chart", { + * "width": 500, + * "height": 600, + * "margin": [10, 10, 20, 70] + * }); + * //Create bullet with log base; + * var log = new Bullet("chart2", { + * width: 300, + * height: 60, + * margin: [10, 10, 20, 70], + * backgroundColor: ["#66f", "#ddf"], + * measureColor: ["#000", "#000"], + * markerColor: "#44f", + * axisStyle: "log", + * logBase: 10 + * }); + * ``` + * @param {Mix} node The dom node or dom node Id + * @param {Object} options options json object for determin stream style. + */ + var Bullet = DataV.extend(DataV.Chart, { + initialize: function (node, options) { + this.type = "Bullet"; + this.node = this.checkContainer(node); + // Properties + this.defaults.orient = "horizonal"; // "horizonal", "vertical" + this.defaults.axisStyle = "linear"; // "linear", "log" + this.defaults.logBase = Math.E; + this.defaults.tickDivide = 5; + this.defaults.margin = [10, 10, 20, 80];//top, right, bottom, left + this.defaults.centerBarRatio = 0.3; + this.defaults.markerWidth = 4; + this.defaults.markerRatio = 0.7; + this.defaults.titleRatio = 0.6; //title's text height : subtitle's text height = 6:4 + this.defaults.backgroundColor = ["#666", "#ddd"]; //dark, light + this.defaults.measureColor = ["steelblue", "#B0C4DE"]; //dark, light + this.defaults.markerColor = "#000"; + + // canvas + this.defaults.width = 200; + this.defaults.height = 80; + + this.setOptions(options); + this.createCanvas(); + } + }); + + /*! + * 创建画布 + */ + Bullet.prototype.createCanvas = function () { + var conf = this.defaults; + this.canvas = new Raphael(this.node, conf.width, conf.height); + }; + + /** + * 设置数据源 + * Examples: + * ``` + * bullet.setSource({ + * title: "Sample", + * subtitle: "ratio", + * ranges: [0, 0.5, 0.8, 1], + * measures: [0.7, 0.9], + * markers: [0.6], + * rangeTitles: ["below 50%", "top 20% - 50%", "top 20%"], + * measureTitles: ["value is 0.7", "value is 0.9"], + * markerTitles: ["mean is 0.6"] + * }) + * ``` + * @param {Object} source 数据源 + */ + Bullet.prototype.setSource = function (source) { + var conf = this.defaults, + range, + axisOrient; + this.data = source; + if (conf.orient === "horizonal") { + axisOrient = "bottom"; + range = [conf.margin[3], conf.width - conf.margin[1]]; + } else if (conf.orient === "vertical") { + axisOrient = "left"; + range = [conf.height - conf.margin[2], conf.margin[0]]; + } + + if (conf.axisStyle === "linear") { + this.scale = d3.scale.linear(); + } else if (conf.axisStyle === "log") { + this.scale = d3.scale.log(); + } + + this.data.min = this.data.ranges[0]; + this.data.max = this.data.ranges[this.data.ranges.length - 1]; + this.scale.domain([this.data.min, this.data.max]) + .range(range); + + if (conf.axisStyle === "linear") { + this.axis = Axis().scale(this.scale).orient(axisOrient).ticks(conf.tickDivide).domainAttr({"stroke": "none"}); + } else if (conf.axisStyle === "log") { + this.logScale = d3.scale.linear() + .domain([Math.log(this.data.min)/Math.log(conf.logBase), Math.log(this.data.max)/Math.log(conf.logBase)]) + .range(range); + this.axis = Axis() + .orient(axisOrient) + .scale(this.logScale) + .ticks(conf.tickDivide) + .tickFormat(function (d) {return Math.round(Math.pow(conf.logBase, d));}) + .domainAttr({"stroke": "none"}); + } + }; + + /*! + * generate bullet dom path + */ + Bullet.prototype.generatePaths = function () { + var conf = this.defaults; + //get color function + if (conf.backgroundColor) { + this.color = d3.interpolateRgb.apply(null, [conf.backgroundColor[0], conf.backgroundColor[1]]); + } + if (conf.measureColor) { + this.measureColor = d3.interpolateRgb.apply(null, [conf.measureColor[0], conf.measureColor[1]]); + } + + if (conf.orient === "horizonal") { + this.paintHorizonal(); + } else if (conf.orient === "vertical") { + this.paintVertical(); + } + }; + + /*! + * paint orient horizonal bullet + */ + Bullet.prototype.paintHorizonal = function () { + var conf = this.defaults; + var paper = this.canvas, + data = this.data, + m = conf.margin, + ranges = [], + measures = [], + markers = [], + rangeTitles = [], + i, + l, + rect, + titleRatio, + w, + h = conf.height - m[0] - m[2], + left; + + //axis + this.axis(paper).attr({transform: "t" + 0 + ',' + (conf.height - m[2])}); + //color rect + ranges = data.ranges; + if (data.rangeTitles) { + rangeTitles = data.rangeTitles; + } + left = m[3]; + for (i = 0, l = ranges.length - 1; i < l; i++) { + w = this.scale(ranges[i + 1]) - this.scale(ranges[i]); + rect = paper.rect(left, m[0], w, h) + .attr({"stroke": "none", + "fill": this.color(l === 1 ? 1 : i / (l - 1)), + "title": rangeTitles[i] ? rangeTitles[i] : ""}); + left += w; + } + + //measure bar + data.measures.forEach(function (d, i) { + var mTitles = data.measureTitles; + var mTitle = mTitles && mTitles[i] ? mTitles[i] : ""; + measures.push({measure: d, measureTitle: mTitle}); + }); + measures.sort(function (a, b) { return d3.ascending(a.measure, b.measure); }); + left = this.scale(data.min); + for (i = 0, l = measures.length; i < l; i++) { + value = Math.max(data.min, Math.min(data.max, measures[i].measure)); + w = this.scale(value) - left; + paper.rect(left, + m[0] + h * (1 - conf.centerBarRatio) / 2, + w, + h * conf.centerBarRatio) + .attr({"stroke": "none", "fill": this.measureColor(l === 1 ? 1 : i / (l - 1)), "title": measures[i].measureTitle}); + left += w; + } + + //marker bar + markers = data.markers; + for (i = 0, l = markers.length; i < l; i++) { + paper.rect(this.scale(markers[i]) - conf.markerWidth / 2, + m[0] + h * (1 - conf.markerRatio) / 2, + conf.markerWidth, + h * conf.markerRatio) + .attr({"stroke": "none", "fill": conf.markerColor, + "title": data.markerTitles && data.markerTitles[i] ? data.markerTitles[i] : ""}); + } + + //title + if (data.title) { + titleRatio = data.subtitle ? conf.titleRatio : 1; + this.title = paper.text(m[3] - 5, m[0] + h / 2, data.title) + .attr({"text-anchor": "end", "font-weight": "bold", "font-size": h * titleRatio * 0.9}); + } + + //subtitle + if (data.subtitle) { + this.subtitle = paper.text(m[3] - 5, conf.height - m[2], data.subtitle) + .attr({"text-anchor": "end", "font-size": h * (1 - conf.titleRatio) * 0.9}); + } + }; + + /*! + * paint orient vertical bullet + */ + Bullet.prototype.paintVertical = function () { + var conf = this.defaults; + var paper = this.canvas, + data = this.data, + m = conf.margin, + ranges = [], + measures = [], + markers = [], + rangeTitles = [], + i, + l, + rect, + titleRatio, + w = conf.width - m[1] - m[3], + h, + bottom; + + //axis + this.axis(paper).attr({transform: "t" + m[3] + ',' + 0}); + + //color rect + ranges = data.ranges; + if (data.rangeTitles) { + rangeTitles = data.rangeTitles; + } + bottom = conf.height - m[2]; + for (i = 0, l = ranges.length - 1; i < l; i++) { + h = -this.scale(ranges[i + 1]) + this.scale(ranges[i]); + rect = paper.rect(m[3], bottom - h, w, h) + .attr({"stroke": "none", + "fill": this.color(l === 1 ? 1 : i / (l - 1)), + "title": rangeTitles[i] ? rangeTitles[i] : ""}); + bottom -= h; + } + + //measure bar + data.measures.forEach(function (d, i) { + var mTitles = data.measureTitles; + var mTitle = mTitles && mTitles[i] ? mTitles[i] : ""; + measures.push({measure: d, measureTitle: mTitle}); + }); + measures.sort(function (a, b) { return d3.ascending(a.measure, b.measure); }); + bottom = this.scale(data.min); + for (i = 0, l = measures.length; i < l; i++) { + value = Math.max(data.min, Math.min(data.max, measures[i].measure)); + h = -this.scale(value) + bottom; + paper.rect(m[3] + w * (1 - conf.centerBarRatio) / 2, + bottom - h, + w * conf.centerBarRatio, + h) + .attr({"stroke": "none", "fill": this.measureColor(l === 1 ? 1 : i / (l - 1)), "title": measures[i].measureTitle}); + bottom -= h; + } + + //marker bar + markers = data.markers; + for (i = 0, l = markers.length; i < l; i++) { + paper.rect(m[3] + w * (1 - conf.markerRatio) / 2, + this.scale(markers[i]) - conf.markerWidth / 2, + w * conf.markerRatio, + conf.markerWidth) + .attr({"stroke": "none", "fill": conf.markerColor, + "title": data.markerTitles && data.markerTitles[i] ? data.markerTitles[i] : ""}); + } + + //title + if (data.title) { + titleRatio = data.subtitle ? conf.titleRatio : 1; + m[0] *= 0.9; //some ratio adjust; + this.title = paper.text((conf.width + m[3] - m[1])/ 2, m[0] * titleRatio / 2, data.title) + .attr({"text-anchor": "middle", "font-weight": "bold", "font-size": m[0] * titleRatio * 0.8}); + } + + //subtitle + if (data.subtitle) { + this.subtitle = paper.text((conf.width + m[3] - m[1])/ 2, m[0] * (1 - (1 - titleRatio) / 2), data.subtitle) + .attr({"text-anchor": "middle", "font-size": m[0] * (1 - titleRatio) * 0.8}); + } + }; + + /*! + * clean canvas + */ + Bullet.prototype.clearCanvas = function () { + this.canvas.clear(); + }; + + /** + * render bullet + */ + Bullet.prototype.render = function (options) { + this.setOptions(options); + this.generatePaths(); + }; + + return Bullet; +}); + diff --git a/lib/charts/bundle.js b/lib/charts/bundle.js index 24e7b07..1e909c0 100644 --- a/lib/charts/bundle.js +++ b/lib/charts/bundle.js @@ -1,424 +1,424 @@ -/*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; -}); +/*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; +}); diff --git a/lib/charts/chinamap.js b/lib/charts/chinamap.js index ee9452a..fc03eb6 100644 --- a/lib/charts/chinamap.js +++ b/lib/charts/chinamap.js @@ -1,1164 +1,1164 @@ -/*global d3, $, define */ -/*! - * Chinamap兼容定义部分 - */ -;(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];}); - } -})('Chinamap', function (require) { - var DataV = require('DataV'); - - /* - * Chinamap构造函数,继承自Chart - * Options: - * - * - `width` 数字,图片宽度,默认为750,表示图片高750px - * - `height` 数字,图片高度,默认为500 - * - `showBackTag` 布尔值,回退操作导航条是否显示,默认为 true, 显示;设为false则不显示 - * - `backHeight` 数字,回退操作导航条宽度,默认为20 - * - `level1BorderWidth` 数字,一级方框的边框宽度,默认为1(1px),不建议修改 - * - `level2BorderWidth` 数字,二级方框的边框宽度,默认为1(1px),不建议修改 - * - `fontSizeRatio` 数字,表示图中文字大小。默认为1.0(1倍), 若设为2.0,字体大小会加倍; - * - `customEvent` 函数对象,其中有4个自定义函数。`leafNodeClick` 函数,表示点击叶子节点的事件响应,默认为空函数; `hoverIn` 函数,表示鼠标移进方框的事件响应,默认为空函数; `hoverOut` 函数,表示鼠标移出方框的事件响应,默认为空函数; `mouseover` 函数,表示在方框内移动鼠标的事件响应,默认为设置浮框的内容,可以替换它修改浮框内容; 这些函数可以在创建对象或setOption()时一起设置,也可以通过on函数单独设置。 - * - * Examples: - * create chinamap in a dom node with id "chart", width is 500; height is 600px; - * ``` - * var chinamap = new Chinamap("chart", {"width": 500, "height": 600}); - * ``` - * @param {Object} node The dom node or dom node Id - * @param {Object} options JSON object for determin chinamap style - */ - var Chinamap = DataV.extend(DataV.Chart, { - initialize: function (node, options) { - var noop = function () {}; - this.type = "Chinamap"; - this.node = this.checkContainer(node); - - // Properties - /* - this.floatTag;//浮框对象,这是个可操作的对象。 - */ - this.viewBox = []; - this.mapCache = {}; - this.states = []; - this.words = []; - this.projection = function () {}; // d3 map prejection function - this.getAreaPath = function () {}; // input geojson feature, return area path string - this.colorGenerator = function () {}; // produce gradient or discrete color; - this.sourceData = {}; - this.geoData = {}; - this.geoData.areaIdIndex = { - '全国': 0, - '新疆': 65, - '西藏': 54, - '内蒙古': 15, - '青海': 63, - '四川': 51, - '黑龙江': 23, - '甘肃': 62, - '云南': 53, - '广西': 45, - '湖南': 43, - '陕西': 61, - '广东': 44, - '吉林': 22, - '河北': 13, - '湖北': 42, - '贵州': 52, - '山东': 37, - '江西': 36, - '河南': 41, - '辽宁': 21, - '山西': 14, - '安徽': 34, - '福建': 35, - '浙江': 33, - '江苏': 32, - '重庆': 50, - '宁夏': 64, - '海南': 46, - '台湾': 71, - '北京': 11, - '天津': 12, - '上海': 31, - '香港': 81, - '澳门': 82 - }; - this.geoData.areaBoxes = { - //x, y, width, height when projection scale is 4000 - 0: [-1174.6445229087194, -1437.3577680805693, 3039.3970214233723, 2531.19589698184], - 65: [-1174.9404317915883, -1136.0130934711678, 1216.4169237052663, 939.4360818385251], - 54: [-1061.2905098655508, -273.40253896102865, 1182.4138890465167, 728.4762434212385], - 15: [81.92106433333947, -1404.5655158641246, 1337.913665139638, 1168.7030286278964], - 63: [-398.0407413665446, -404.86540158240564, 770.5429460357634, 553.4881569694239], - 51: [34.77351011413543, -24.727858097581816, 654.265749584143, 581.5837904142871], - 23: [1185.0861642873883, -1435.9087566254907, 680.9449423479143, 618.3772597960831], - 62: [-197.5222870378875, -631.2015222269291, 884.6861134736321, 734.2542202456989], - 53: [-4.030270169151834, 326.89754492870105, 561.4971786143803, 565.9079094851168], - 45: [444.4355364538484, 524.7911424174906, 490.6548359068431, 384.1667316158848], - 43: [716.7125751678784, 265.3988842488122, 346.1702652872375, 377.50144051998274], - 61: [508.5948583446903, -399.56997062473215, 321.038690321553, 559.1002147021181], - 44: [790.2032875493967, 572.9640361040085, 494.8279567104971, 388.7112686526252], - 22: [1287.5729431804648, -950.943295028444, 504.33243011403374, 354.162667814153], - 13: [940.0156020671719, -646.4007207319194, 325.33903805510784, 477.4542727272415], - 42: [683.8325394595918, 45.82949601748078, 468.66717545627034, 295.2142095820616], - 52: [392.5021834497175, 337.4483828727408, 375.50579966539516, 320.9420464446699], - 37: [1035.7855473594757, -382.19242168799906, 412.5747391303373, 313.152767793266], - 36: [1012.6841751377355, 236.50140310944056, 295.599802392515, 400.86430917822287], - 41: [785.5419798731749, -185.2911232263814, 362.6977821251186, 340.3902676066224], - 21: [1203.0641741691293, -757.0946871553339, 352.71788824534656, 357.71276541155214], - 14: [776.5185040689469, -493.6204506126494, 212.68572802329425, 448.08485211774945], - 34: [1054.014965660052, -80.43770626104327, 295.73127466484925, 352.03731065611606], - 35: [1172.0955040211252, 341.81292779438445, 288.99462739279807, 339.42845011348845], - 33: [1272.1789620983063, 123.46272678646208, 286.17816622252326, 286.73860446060394], - 32: [1125.161343490302, -134.97368204682834, 356.1806346879009, 291.4961628010442], - 50: [497.78832088614774, 127.0051229616378, 291.91221530072164, 280.8880182020781], - 64: [441.193675072408, -376.31946967355213, 183.76989823787306, 293.0024551112753], - 46: [723.8031601361929, 946.050886515855, 183.33374783084207, 147.66048518654895], - 71: [1459.925544038912, 519.7445429876257, 103.06085087505835, 237.80851484008463], - 11: [1031.6052083127613, -530.1928574952913, 103.23943439987329, 114.66079087790081], - 12: [1106.9649995752443, -479.16508616378724, 71.21176554916747, 120.01987096046025], - 31: [1420.334836525578, 71.79837578328207, 70.41721601016525, 81.99461244072737], - 81: [1061.983645387268, 769.0837862603122, 50.65584483626753, 32.17422147262721], - 82: [1043.1350056914507, 798.0786255550063, 5.387452843479423, 7.564113979470676] - }; - this.geoData.provinceIndex = { - '新疆': {'loc': [84.9023,41.748], 'fullName': '新疆'}, - '西藏': {'loc': [88.7695,31.6846], 'fullName': '西藏'}, - '内蒙': {'loc': [117.5977,44.3408], 'fullName': '内蒙古'}, - '青海': {'loc': [96.2402,35.4199], 'fullName': '青海'}, - '四川': {'loc': [102.9199,30.1904], 'fullName': '四川'}, - '黑龙': {'loc': [128.1445,48.5156], 'fullName': '黑龙江'}, - '甘肃': {'loc': [95.7129,40.166], 'fullName': '甘肃'}, - '云南': {'loc': [101.8652,25.1807], 'fullName': '云南'}, - '广西': {'loc': [108.2813,23.6426], 'fullName': '广西'}, - '湖南': {'loc': [111.5332,27.3779], 'fullName': '湖南'}, - '陕西': {'loc': [109.5996,35.6396], 'fullName': '陕西'}, - '广东': {'loc': [113.4668,22.8076], 'fullName': '广东'}, - '吉林': {'loc': [126.4746,43.5938], 'fullName': '吉林'}, - '河北': {'loc': [115.4004,37.9688], 'fullName': '河北'}, - '湖北': {'loc': [112.2363,31.1572], 'fullName': '湖北'}, - '贵州': {'loc': [106.6113,26.9385], 'fullName': '贵州'}, - '山东': {'loc': [118.7402,36.4307], 'fullName': '山东'}, - '江西': {'loc': [116.0156,27.29], 'fullName': '江西'}, - '河南': {'loc': [113.4668,33.8818], 'fullName': '河南'}, - '辽宁': {'loc': [122.3438,41.0889], 'fullName': '辽宁'}, - '山西': {'loc': [112.4121,37.6611], 'fullName': '山西'}, - '安徽': {'loc': [117.2461,32.0361], 'fullName': '安徽'}, - '福建': {'loc': [118.3008,25.9277], 'fullName': '福建'}, - '浙江': {'loc': [120.498,29.0918], 'fullName': '浙江'}, - '江苏': {'loc': [120.0586,32.915], 'fullName': '江苏'}, - '重庆': {'loc': [107.7539,30.1904], 'fullName': '重庆'}, - '宁夏': {'loc': [105.9961,37.3096], 'fullName': '宁夏'}, - '海南': {'loc': [109.9512,19.2041], 'fullName': '海南'}, - '台湾': {'loc': [121.0254,23.5986], 'fullName': '台湾'}, - '北京': {'loc': [116.4551,40.2539], 'fullName': '北京'}, - '天津': {'loc': [117.4219,39.4189], 'fullName': '天津'}, - '上海': {'loc': [121.4648,31.2891], 'fullName': '上海'}, - '香港': {'loc': [114.2578,22.3242], 'fullName': '香港'}, - '澳门': {'loc': [113.5547,22.1484], 'fullName': '澳门'} - }; - this.geoData.cityIndex = { - '重庆': {'loc': [107.7539,30.1904], 'fullName': '重庆'}, - '北京': {'loc': [116.4551,40.2539], 'fullName': '北京'}, - '天津': {'loc': [117.4219,39.4189], 'fullName': '天津'}, - '上海': {'loc': [121.4648,31.2891], 'fullName': '上海'}, - '香港': {'loc': [114.2578,22.3242], 'fullName': '香港'}, - '澳门': {'loc': [113.5547,22.1484], 'fullName': '澳门'}, - '巴音': {'loc': [88.1653,39.6002], 'fullName': '巴音郭楞蒙古自治州'}, - '和田': {'loc': [81.167,36.9855], 'fullName': '和田地区'}, - '哈密': {'loc': [93.7793,42.9236], 'fullName': '哈密地区'}, - '阿克': {'loc': [82.9797,41.0229], 'fullName': '阿克苏地区'}, - '阿勒': {'loc': [88.2971,47.0929], 'fullName': '阿勒泰地区'}, - '喀什': {'loc': [77.168,37.8534], 'fullName': '喀什地区'}, - '塔城': {'loc': [86.6272,45.8514], 'fullName': '塔城地区'}, - '昌吉': {'loc': [89.6814,44.4507], 'fullName': '昌吉回族自治州'}, - '克孜': {'loc': [74.6301,39.5233], 'fullName': '克孜勒苏柯尔克孜自治州'}, - '吐鲁': {'loc': [89.6375,42.4127], 'fullName': '吐鲁番地区'}, - '伊犁': {'loc': [82.5513,43.5498], 'fullName': '伊犁哈萨克自治州'}, - '博尔': {'loc': [81.8481,44.6979], 'fullName': '博尔塔拉蒙古自治州'}, - '乌鲁': {'loc': [87.9236,43.5883], 'fullName': '乌鲁木齐市'}, - '克拉': {'loc': [85.2869,45.5054], 'fullName': '克拉玛依市'}, - '阿拉尔': {'loc': [81.2769,40.6549], 'fullName': '阿拉尔市'}, - '图木': {'loc': [79.1345,39.8749], 'fullName': '图木舒克市'}, - '五家': {'loc': [87.5391,44.3024], 'fullName': '五家渠市'}, - '石河': {'loc': [86.0229,44.2914], 'fullName': '石河子市'}, - '那曲': {'loc': [88.1982,33.3215], 'fullName': '那曲地区'}, - '阿里': {'loc': [82.3645,32.7667], 'fullName': '阿里地区'}, - '日喀': {'loc': [86.2427,29.5093], 'fullName': '日喀则地区'}, - '林芝': {'loc': [95.4602,29.1138], 'fullName': '林芝地区'}, - '昌都': {'loc': [97.0203,30.7068], 'fullName': '昌都地区'}, - '山南': {'loc': [92.2083,28.3392], 'fullName': '山南地区'}, - '拉萨': {'loc': [91.1865,30.1465], 'fullName': '拉萨市'}, - '呼伦': {'loc': [120.8057,50.2185], 'fullName': '呼伦贝尔市'}, - '阿拉善': {'loc': [102.019,40.1001], 'fullName': '阿拉善盟'}, - '锡林': {'loc': [115.6421,44.176], 'fullName': '锡林郭勒盟'}, - '鄂尔': {'loc': [108.9734,39.2487], 'fullName': '鄂尔多斯市'}, - '赤峰': {'loc': [118.6743,43.2642], 'fullName': '赤峰市'}, - '巴彦': {'loc': [107.5562,41.3196], 'fullName': '巴彦淖尔市'}, - '通辽': {'loc': [121.4758,43.9673], 'fullName': '通辽市'}, - '乌兰': {'loc': [112.5769,41.77], 'fullName': '乌兰察布市'}, - '兴安': {'loc': [121.3879,46.1426], 'fullName': '兴安盟'}, - '包头': {'loc': [110.3467,41.4899], 'fullName': '包头市'}, - '呼和': {'loc': [111.4124,40.4901], 'fullName': '呼和浩特市'}, - '乌海': {'loc': [106.886,39.4739], 'fullName': '乌海市'}, - '海西': {'loc': [94.9768,37.1118], 'fullName': '海西蒙古族藏族自治州'}, - '玉树': {'loc': [93.5925,33.9368], 'fullName': '玉树藏族自治州'}, - '果洛': {'loc': [99.3823,34.0466], 'fullName': '果洛藏族自治州'}, - '海南': {'loc': [100.3711,35.9418], 'fullName': '海南藏族自治州'}, - '海北': {'loc': [100.3711,37.9138], 'fullName': '海北藏族自治州'}, - '黄南': {'loc': [101.5686,35.1178], 'fullName': '黄南藏族自治州'}, - '海东': {'loc': [102.3706,36.2988], 'fullName': '海东地区'}, - '西宁': {'loc': [101.4038,36.8207], 'fullName': '西宁市'}, - '甘孜': {'loc': [99.9207,31.0803], 'fullName': '甘孜藏族自治州'}, - '阿坝': {'loc': [102.4805,32.4536], 'fullName': '阿坝藏族羌族自治州'}, - '凉山': {'loc': [101.9641,27.6746], 'fullName': '凉山彝族自治州'}, - '绵阳': {'loc': [104.7327,31.8713], 'fullName': '绵阳市'}, - '达州': {'loc': [107.6111,31.333], 'fullName': '达州市'}, - '广元': {'loc': [105.6885,32.2284], 'fullName': '广元市'}, - '雅安': {'loc': [102.6672,29.8938], 'fullName': '雅安市'}, - '宜宾': {'loc': [104.6558,28.548], 'fullName': '宜宾市'}, - '乐山': {'loc': [103.5791,29.1742], 'fullName': '乐山市'}, - '南充': {'loc': [106.2048,31.1517], 'fullName': '南充市'}, - '巴中': {'loc': [107.0618,31.9977], 'fullName': '巴中市'}, - '泸州': {'loc': [105.4578,28.493], 'fullName': '泸州市'}, - '成都': {'loc': [103.9526,30.7617], 'fullName': '成都市'}, - '资阳': {'loc': [104.9744,30.1575], 'fullName': '资阳市'}, - '攀枝': {'loc': [101.6895,26.7133], 'fullName': '攀枝花市'}, - '眉山': {'loc': [103.8098,30.0146], 'fullName': '眉山市'}, - '广安': {'loc': [106.6333,30.4376], 'fullName': '广安市'}, - '德阳': {'loc': [104.48,31.1133], 'fullName': '德阳市'}, - '内江': {'loc': [104.8535,29.6136], 'fullName': '内江市'}, - '遂宁': {'loc': [105.5347,30.6683], 'fullName': '遂宁市'}, - '自贡': {'loc': [104.6667,29.2786], 'fullName': '自贡市'}, - '黑河': {'loc': [127.1448,49.2957], 'fullName': '黑河市'}, - '大兴': {'loc': [124.1016,52.2345], 'fullName': '大兴安岭地区'}, - '哈尔': {'loc': [127.9688,45.368], 'fullName': '哈尔滨市'}, - '齐齐': {'loc': [124.541,47.5818], 'fullName': '齐齐哈尔市'}, - '牡丹': {'loc': [129.7815,44.7089], 'fullName': '牡丹江市'}, - '绥化': {'loc': [126.7163,46.8018], 'fullName': '绥化市'}, - '伊春': {'loc': [129.1992,47.9608], 'fullName': '伊春市'}, - '佳木': {'loc': [133.0005,47.5763], 'fullName': '佳木斯市'}, - '鸡西': {'loc': [132.7917,45.7361], 'fullName': '鸡西市'}, - '双鸭': {'loc': [133.5938,46.7523], 'fullName': '双鸭山市'}, - '大庆': {'loc': [124.7717,46.4282], 'fullName': '大庆市'}, - '鹤岗': {'loc': [130.4407,47.7081], 'fullName': '鹤岗市'}, - '七台': {'loc': [131.2756,45.9558], 'fullName': '七台河市'}, - '酒泉': {'loc': [96.2622,40.4517], 'fullName': '酒泉市'}, - '张掖': {'loc': [99.7998,38.7433], 'fullName': '张掖市'}, - '甘南': {'loc': [102.9199,34.6893], 'fullName': '甘南藏族自治州'}, - '武威': {'loc': [103.0188,38.1061], 'fullName': '武威市'}, - '陇南': {'loc': [105.304,33.5632], 'fullName': '陇南市'}, - '庆阳': {'loc': [107.5342,36.2], 'fullName': '庆阳市'}, - '白银': {'loc': [104.8645,36.5076], 'fullName': '白银市'}, - '定西': {'loc': [104.5569,35.0848], 'fullName': '定西市'}, - '天水': {'loc': [105.6445,34.6289], 'fullName': '天水市'}, - '兰州': {'loc': [103.5901,36.3043], 'fullName': '兰州市'}, - '平凉': {'loc': [107.0728,35.321], 'fullName': '平凉市'}, - '临夏': {'loc': [103.2715,35.5737], 'fullName': '临夏回族自治州'}, - '金昌': {'loc': [102.074,38.5126], 'fullName': '金昌市'}, - '嘉峪': {'loc': [98.1738,39.8035], 'fullName': '嘉峪关市'}, - '普洱': {'loc': [100.7446,23.4229], 'fullName': '普洱市'}, - '红河': {'loc': [103.0408,23.6041], 'fullName': '红河哈尼族彝族自治州'}, - '文山': {'loc': [104.8865,23.5712], 'fullName': '文山壮族苗族自治州'}, - '曲靖': {'loc': [103.9417,25.7025], 'fullName': '曲靖市'}, - '楚雄': {'loc': [101.6016,25.3619], 'fullName': '楚雄彝族自治州'}, - '大理': {'loc': [99.9536,25.6805], 'fullName': '大理白族自治州'}, - '临沧': {'loc': [99.613,24.0546], 'fullName': '临沧市'}, - '迪庆': {'loc': [99.4592,27.9327], 'fullName': '迪庆藏族自治州'}, - '昭通': {'loc': [104.0955,27.6031], 'fullName': '昭通市'}, - '昆明': {'loc': [102.9199,25.4663], 'fullName': '昆明市'}, - '丽江': {'loc': [100.448,26.955], 'fullName': '丽江市'}, - '西双': {'loc': [100.8984,21.8628], 'fullName': '西双版纳傣族自治州'}, - '保山': {'loc': [99.0637,24.9884], 'fullName': '保山市'}, - '玉溪': {'loc': [101.9312,23.8898], 'fullName': '玉溪市'}, - '怒江': {'loc': [99.1516,26.5594], 'fullName': '怒江傈僳族自治州'}, - '德宏': {'loc': [98.1299,24.5874], 'fullName': '德宏傣族景颇族自治州'}, - '百色': {'loc': [106.6003,23.9227], 'fullName': '百色市'}, - '河池': {'loc': [107.8638,24.5819], 'fullName': '河池市'}, - '桂林': {'loc': [110.5554,25.318], 'fullName': '桂林市'}, - '南宁': {'loc': [108.479,23.1152], 'fullName': '南宁市'}, - '柳州': {'loc': [109.3799,24.9774], 'fullName': '柳州市'}, - '崇左': {'loc': [107.3364,22.4725], 'fullName': '崇左市'}, - '来宾': {'loc': [109.7095,23.8403], 'fullName': '来宾市'}, - '玉林': {'loc': [110.2148,22.3792], 'fullName': '玉林市'}, - '梧州': {'loc': [110.9949,23.5052], 'fullName': '梧州市'}, - '贺州': {'loc': [111.3135,24.4006], 'fullName': '贺州市'}, - '钦州': {'loc': [109.0283,22.0935], 'fullName': '钦州市'}, - '贵港': {'loc': [109.9402,23.3459], 'fullName': '贵港市'}, - '防城': {'loc': [108.0505,21.9287], 'fullName': '防城港市'}, - '北海': {'loc': [109.314,21.6211], 'fullName': '北海市'}, - '怀化': {'loc': [109.9512,27.4438], 'fullName': '怀化市'}, - '永州': {'loc': [111.709,25.752], 'fullName': '永州市'}, - '邵阳': {'loc': [110.9619,26.8121], 'fullName': '邵阳市'}, - '郴州': {'loc': [113.2361,25.8673], 'fullName': '郴州市'}, - '常德': {'loc': [111.4014,29.2676], 'fullName': '常德市'}, - '湘西': {'loc': [109.7864,28.6743], 'fullName': '湘西土家族苗族自治州'}, - '衡阳': {'loc': [112.4121,26.7902], 'fullName': '衡阳市'}, - '岳阳': {'loc': [113.2361,29.1357], 'fullName': '岳阳市'}, - '益阳': {'loc': [111.731,28.3832], 'fullName': '益阳市'}, - '长沙': {'loc': [113.0823,28.2568], 'fullName': '长沙市'}, - '株洲': {'loc': [113.5327,27.0319], 'fullName': '株洲市'}, - '张家界': {'loc': [110.5115,29.328], 'fullName': '张家界市'}, - '娄底': {'loc': [111.6431,27.7185], 'fullName': '娄底市'}, - '湘潭': {'loc': [112.5439,27.7075], 'fullName': '湘潭市'}, - '榆林': {'loc': [109.8743,38.205], 'fullName': '榆林市'}, - '延安': {'loc': [109.1052,36.4252], 'fullName': '延安市'}, - '汉中': {'loc': [106.886,33.0139], 'fullName': '汉中市'}, - '安康': {'loc': [109.1162,32.7722], 'fullName': '安康市'}, - '商洛': {'loc': [109.8083,33.761], 'fullName': '商洛市'}, - '宝鸡': {'loc': [107.1826,34.3433], 'fullName': '宝鸡市'}, - '渭南': {'loc': [109.7864,35.0299], 'fullName': '渭南市'}, - '咸阳': {'loc': [108.4131,34.8706], 'fullName': '咸阳市'}, - '西安': {'loc': [109.1162,34.2004], 'fullName': '西安市'}, - '铜川': {'loc': [109.0393,35.1947], 'fullName': '铜川市'}, - '清远': {'loc': [112.9175,24.3292], 'fullName': '清远市'}, - '韶关': {'loc': [113.7964,24.7028], 'fullName': '韶关市'}, - '湛江': {'loc': [110.3577,20.9894], 'fullName': '湛江市'}, - '梅州': {'loc': [116.1255,24.1534], 'fullName': '梅州市'}, - '河源': {'loc': [114.917,23.9722], 'fullName': '河源市'}, - '肇庆': {'loc': [112.1265,23.5822], 'fullName': '肇庆市'}, - '惠州': {'loc': [114.6204,23.1647], 'fullName': '惠州市'}, - '茂名': {'loc': [111.0059,22.0221], 'fullName': '茂名市'}, - '江门': {'loc': [112.6318,22.1484], 'fullName': '江门市'}, - '阳江': {'loc': [111.8298,22.0715], 'fullName': '阳江市'}, - '云浮': {'loc': [111.7859,22.8516], 'fullName': '云浮市'}, - '广州': {'loc': [113.5107,23.2196], 'fullName': '广州市'}, - '汕尾': {'loc': [115.5762,23.0438], 'fullName': '汕尾市'}, - '揭阳': {'loc': [116.1255,23.313], 'fullName': '揭阳市'}, - '珠海': {'loc': [113.7305,22.1155], 'fullName': '珠海市'}, - '佛山': {'loc': [112.8955,23.1097], 'fullName': '佛山市'}, - '潮州': {'loc': [116.7847,23.8293], 'fullName': '潮州市'}, - '汕头': {'loc': [117.1692,23.3405], 'fullName': '汕头市'}, - '深圳': {'loc': [114.5435,22.5439], 'fullName': '深圳市'}, - '东莞': {'loc': [113.8953,22.901], 'fullName': '东莞市'}, - '中山': {'loc': [113.4229,22.478], 'fullName': '中山市'}, - '延边': {'loc': [129.397,43.2587], 'fullName': '延边朝鲜族自治州'}, - '吉林': {'loc': [126.8372,43.6047], 'fullName': '吉林市'}, - '白城': {'loc': [123.0029,45.2637], 'fullName': '白城市'}, - '松原': {'loc': [124.0906,44.7198], 'fullName': '松原市'}, - '长春': {'loc': [125.8154,44.2584], 'fullName': '长春市'}, - '白山': {'loc': [127.2217,42.0941], 'fullName': '白山市'}, - '通化': {'loc': [125.9583,41.8579], 'fullName': '通化市'}, - '四平': {'loc': [124.541,43.4894], 'fullName': '四平市'}, - '辽源': {'loc': [125.343,42.7643], 'fullName': '辽源市'}, - '承德': {'loc': [117.5757,41.4075], 'fullName': '承德市'}, - '张家口': {'loc': [115.1477,40.8527], 'fullName': '张家口市'}, - '保定': {'loc': [115.0488,39.0948], 'fullName': '保定市'}, - '唐山': {'loc': [118.4766,39.6826], 'fullName': '唐山市'}, - '沧州': {'loc': [116.8286,38.2104], 'fullName': '沧州市'}, - '石家': {'loc': [114.4995,38.1006], 'fullName': '石家庄市'}, - '邢台': {'loc': [114.8071,37.2821], 'fullName': '邢台市'}, - '邯郸': {'loc': [114.4775,36.535], 'fullName': '邯郸市'}, - '秦皇': {'loc': [119.2126,40.0232], 'fullName': '秦皇岛市'}, - '衡水': {'loc': [115.8838,37.7161], 'fullName': '衡水市'}, - '廊坊': {'loc': [116.521,39.0509], 'fullName': '廊坊市'}, - '恩施': {'loc': [109.5007,30.2563], 'fullName': '恩施土家族苗族自治州'}, - '十堰': {'loc': [110.5115,32.3877], 'fullName': '十堰市'}, - '宜昌': {'loc': [111.1707,30.7617], 'fullName': '宜昌市'}, - '襄樊': {'loc': [111.9397,31.9263], 'fullName': '襄樊市'}, - '黄冈': {'loc': [115.2686,30.6628], 'fullName': '黄冈市'}, - '荆州': {'loc': [113.291,30.0092], 'fullName': '荆州市'}, - '荆门': {'loc': [112.6758,30.9979], 'fullName': '荆门市'}, - '咸宁': {'loc': [114.2578,29.6631], 'fullName': '咸宁市'}, - '随州': {'loc': [113.4338,31.8768], 'fullName': '随州市'}, - '孝感': {'loc': [113.9502,31.1188], 'fullName': '孝感市'}, - '武汉': {'loc': [114.3896,30.6628], 'fullName': '武汉市'}, - '黄石': {'loc': [115.0159,29.9213], 'fullName': '黄石市'}, - '神农': {'loc': [110.4565,31.5802], 'fullName': '神农架林区'}, - '天门': {'loc': [113.0273,30.6409], 'fullName': '天门市'}, - '仙桃': {'loc': [113.3789,30.3003], 'fullName': '仙桃市'}, - '潜江': {'loc': [112.7637,30.3607], 'fullName': '潜江市'}, - '鄂州': {'loc': [114.7302,30.4102], 'fullName': '鄂州市'}, - '遵义': {'loc': [106.908,28.1744], 'fullName': '遵义市'}, - '黔东': {'loc': [108.4241,26.4166], 'fullName': '黔东南苗族侗族自治州'}, - '毕节': {'loc': [105.1611,27.0648], 'fullName': '毕节地区'}, - '黔南': {'loc': [107.2485,25.8398], 'fullName': '黔南布依族苗族自治州'}, - '铜仁': {'loc': [108.6218,28.0096], 'fullName': '铜仁地区'}, - '黔西': {'loc': [105.5347,25.3949], 'fullName': '黔西南布依族苗族自治州'}, - '六盘': {'loc': [104.7546,26.0925], 'fullName': '六盘水市'}, - '安顺': {'loc': [105.9082,25.9882], 'fullName': '安顺市'}, - '贵阳': {'loc': [106.6992,26.7682], 'fullName': '贵阳市'}, - '烟台': {'loc': [120.7397,37.5128], 'fullName': '烟台市'}, - '临沂': {'loc': [118.3118,35.2936], 'fullName': '临沂市'}, - '潍坊': {'loc': [119.0918,36.524], 'fullName': '潍坊市'}, - '青岛': {'loc': [120.4651,36.3373], 'fullName': '青岛市'}, - '菏泽': {'loc': [115.6201,35.2057], 'fullName': '菏泽市'}, - '济宁': {'loc': [116.8286,35.3375], 'fullName': '济宁市'}, - '德州': {'loc': [116.6858,37.2107], 'fullName': '德州市'}, - '滨州': {'loc': [117.8174,37.4963], 'fullName': '滨州市'}, - '聊城': {'loc': [115.9167,36.4032], 'fullName': '聊城市'}, - '东营': {'loc': [118.7073,37.5513], 'fullName': '东营市'}, - '济南': {'loc': [117.1582,36.8701], 'fullName': '济南市'}, - '泰安': {'loc': [117.0264,36.0516], 'fullName': '泰安市'}, - '威海': {'loc': [121.9482,37.1393], 'fullName': '威海市'}, - '日照': {'loc': [119.2786,35.5023], 'fullName': '日照市'}, - '淄博': {'loc': [118.0371,36.6064], 'fullName': '淄博市'}, - '枣庄': {'loc': [117.323,34.8926], 'fullName': '枣庄市'}, - '莱芜': {'loc': [117.6526,36.2714], 'fullName': '莱芜市'}, - '赣州': {'loc': [115.2795,25.8124], 'fullName': '赣州市'}, - '吉安': {'loc': [114.884,26.9659], 'fullName': '吉安市'}, - '上饶': {'loc': [117.8613,28.7292], 'fullName': '上饶市'}, - '九江': {'loc': [115.4224,29.3774], 'fullName': '九江市'}, - '抚州': {'loc': [116.4441,27.4933], 'fullName': '抚州市'}, - '宜春': {'loc': [115.0159,28.3228], 'fullName': '宜春市'}, - '南昌': {'loc': [116.0046,28.6633], 'fullName': '南昌市'}, - '景德': {'loc': [117.334,29.3225], 'fullName': '景德镇市'}, - '萍乡': {'loc': [113.9282,27.4823], 'fullName': '萍乡市'}, - '鹰潭': {'loc': [117.0813,28.2349], 'fullName': '鹰潭市'}, - '新余': {'loc': [114.95,27.8174], 'fullName': '新余市'}, - '南阳': {'loc': [112.4011,33.0359], 'fullName': '南阳市'}, - '信阳': {'loc': [114.8291,32.0197], 'fullName': '信阳市'}, - '洛阳': {'loc': [112.0605,34.3158], 'fullName': '洛阳市'}, - '驻马': {'loc': [114.1589,32.9041], 'fullName': '驻马店市'}, - '周口': {'loc': [114.873,33.6951], 'fullName': '周口市'}, - '商丘': {'loc': [115.741,34.2828], 'fullName': '商丘市'}, - '三门': {'loc': [110.8301,34.3158], 'fullName': '三门峡市'}, - '新乡': {'loc': [114.2029,35.3595], 'fullName': '新乡市'}, - '平顶': {'loc': [112.9724,33.739], 'fullName': '平顶山市'}, - '郑州': {'loc': [113.4668,34.6234], 'fullName': '郑州市'}, - '安阳': {'loc': [114.5325,36.0022], 'fullName': '安阳市'}, - '开封': {'loc': [114.5764,34.6124], 'fullName': '开封市'}, - '焦作': {'loc': [112.8406,35.1508], 'fullName': '焦作市'}, - '许昌': {'loc': [113.6975,34.0466], 'fullName': '许昌市'}, - '濮阳': {'loc': [115.1917,35.799], 'fullName': '濮阳市'}, - '漯河': {'loc': [113.8733,33.6951], 'fullName': '漯河市'}, - '鹤壁': {'loc': [114.3787,35.744], 'fullName': '鹤壁市'}, - '大连': {'loc': [122.2229,39.4409], 'fullName': '大连市'}, - '朝阳': {'loc': [120.0696,41.4899], 'fullName': '朝阳市'}, - '丹东': {'loc': [124.541,40.4242], 'fullName': '丹东市'}, - '铁岭': {'loc': [124.2773,42.7423], 'fullName': '铁岭市'}, - '沈阳': {'loc': [123.1238,42.1216], 'fullName': '沈阳市'}, - '抚顺': {'loc': [124.585,41.8579], 'fullName': '抚顺市'}, - '葫芦': {'loc': [120.1575,40.578], 'fullName': '葫芦岛市'}, - '阜新': {'loc': [122.0032,42.2699], 'fullName': '阜新市'}, - '锦州': {'loc': [121.6626,41.4294], 'fullName': '锦州市'}, - '鞍山': {'loc': [123.0798,40.6055], 'fullName': '鞍山市'}, - '本溪': {'loc': [124.1455,41.1987], 'fullName': '本溪市'}, - '营口': {'loc': [122.4316,40.4297], 'fullName': '营口市'}, - '辽阳': {'loc': [123.4094,41.1383], 'fullName': '辽阳市'}, - '盘锦': {'loc': [121.9482,41.0449], 'fullName': '盘锦市'}, - '忻州': {'loc': [112.4561,38.8971], 'fullName': '忻州市'}, - '吕梁': {'loc': [111.3574,37.7325], 'fullName': '吕梁市'}, - '临汾': {'loc': [111.4783,36.1615], 'fullName': '临汾市'}, - '晋中': {'loc': [112.7747,37.37], 'fullName': '晋中市'}, - '运城': {'loc': [111.1487,35.2002], 'fullName': '运城市'}, - '大同': {'loc': [113.7854,39.8035], 'fullName': '大同市'}, - '长治': {'loc': [112.8625,36.4746], 'fullName': '长治市'}, - '朔州': {'loc': [113.0713,39.6991], 'fullName': '朔州市'}, - '晋城': {'loc': [112.7856,35.6342], 'fullName': '晋城市'}, - '太原': {'loc': [112.3352,37.9413], 'fullName': '太原市'}, - '阳泉': {'loc': [113.4778,38.0951], 'fullName': '阳泉市'}, - '六安': {'loc': [116.3123,31.8329], 'fullName': '六安市'}, - '安庆': {'loc': [116.7517,30.5255], 'fullName': '安庆市'}, - '滁州': {'loc': [118.1909,32.536], 'fullName': '滁州市'}, - '宣城': {'loc': [118.8062,30.6244], 'fullName': '宣城市'}, - '阜阳': {'loc': [115.7629,32.9919], 'fullName': '阜阳市'}, - '宿州': {'loc': [117.5208,33.6841], 'fullName': '宿州市'}, - '黄山': {'loc': [118.0481,29.9542], 'fullName': '黄山市'}, - '巢湖': {'loc': [117.7734,31.4978], 'fullName': '巢湖市'}, - '亳州': {'loc': [116.1914,33.4698], 'fullName': '亳州市'}, - '池州': {'loc': [117.3889,30.2014], 'fullName': '池州市'}, - '合肥': {'loc': [117.29,32.0581], 'fullName': '合肥市'}, - '蚌埠': {'loc': [117.4109,33.1073], 'fullName': '蚌埠市'}, - '芜湖': {'loc': [118.3557,31.0858], 'fullName': '芜湖市'}, - '淮北': {'loc': [116.6968,33.6896], 'fullName': '淮北市'}, - '淮南': {'loc': [116.7847,32.7722], 'fullName': '淮南市'}, - '马鞍': {'loc': [118.6304,31.5363], 'fullName': '马鞍山市'}, - '铜陵': {'loc': [117.9382,30.9375], 'fullName': '铜陵市'}, - '南平': {'loc': [118.136,27.2845], 'fullName': '南平市'}, - '三明': {'loc': [117.5317,26.3013], 'fullName': '三明市'}, - '龙岩': {'loc': [116.8066,25.2026], 'fullName': '龙岩市'}, - '宁德': {'loc': [119.6521,26.9824], 'fullName': '宁德市'}, - '福州': {'loc': [119.4543,25.9222], 'fullName': '福州市'}, - '漳州': {'loc': [117.5757,24.3732], 'fullName': '漳州市'}, - '泉州': {'loc': [118.3228,25.1147], 'fullName': '泉州市'}, - '莆田': {'loc': [119.0918,25.3455], 'fullName': '莆田市'}, - '厦门': {'loc': [118.1689,24.6478], 'fullName': '厦门市'}, - '丽水': {'loc': [119.5642,28.1854], 'fullName': '丽水市'}, - '杭州': {'loc': [119.5313,29.8773], 'fullName': '杭州市'}, - '温州': {'loc': [120.498,27.8119], 'fullName': '温州市'}, - '宁波': {'loc': [121.5967,29.6466], 'fullName': '宁波市'}, - '舟山': {'loc': [122.2559,30.2234], 'fullName': '舟山市'}, - '台州': {'loc': [121.1353,28.6688], 'fullName': '台州市'}, - '金华': {'loc': [120.0037,29.1028], 'fullName': '金华市'}, - '衢州': {'loc': [118.6853,28.8666], 'fullName': '衢州市'}, - '绍兴': {'loc': [120.564,29.7565], 'fullName': '绍兴市'}, - '嘉兴': {'loc': [120.9155,30.6354], 'fullName': '嘉兴市'}, - '湖州': {'loc': [119.8608,30.7782], 'fullName': '湖州市'}, - '盐城': {'loc': [120.2234,33.5577], 'fullName': '盐城市'}, - '徐州': {'loc': [117.5208,34.3268], 'fullName': '徐州市'}, - '南通': {'loc': [121.1023,32.1625], 'fullName': '南通市'}, - '淮安': {'loc': [118.927,33.4039], 'fullName': '淮安市'}, - '苏州': {'loc': [120.6519,31.3989], 'fullName': '苏州市'}, - '宿迁': {'loc': [118.5535,33.7775], 'fullName': '宿迁市'}, - '连云': {'loc': [119.1248,34.552], 'fullName': '连云港市'}, - '扬州': {'loc': [119.4653,32.8162], 'fullName': '扬州市'}, - '南京': {'loc': [118.8062,31.9208], 'fullName': '南京市'}, - '泰州': {'loc': [120.0586,32.5525], 'fullName': '泰州市'}, - '无锡': {'loc': [120.3442,31.5527], 'fullName': '无锡市'}, - '常州': {'loc': [119.4543,31.5582], 'fullName': '常州市'}, - '镇江': {'loc': [119.4763,31.9702], 'fullName': '镇江市'}, - '吴忠': {'loc': [106.853,37.3755], 'fullName': '吴忠市'}, - '中卫': {'loc': [105.4028,36.9525], 'fullName': '中卫市'}, - '固原': {'loc': [106.1389,35.9363], 'fullName': '固原市'}, - '银川': {'loc': [106.3586,38.1775], 'fullName': '银川市'}, - '石嘴': {'loc': [106.4795,39.0015], 'fullName': '石嘴山市'}, - '儋州': {'loc': [109.3291,19.5653], 'fullName': '儋州市'}, - '文昌': {'loc': [110.8905,19.7823], 'fullName': '文昌市'}, - '乐东': {'loc': [109.0283,18.6301], 'fullName': '乐东黎族自治县'}, - '三亚': {'loc': [109.3716,18.3698], 'fullName': '三亚市'}, - '琼中': {'loc': [109.8413,19.0736], 'fullName': '琼中黎族苗族自治县'}, - '东方': {'loc': [108.8498,19.0414], 'fullName': '东方市'}, - '海口': {'loc': [110.3893,19.8516], 'fullName': '海口市'}, - '万宁': {'loc': [110.3137,18.8388], 'fullName': '万宁市'}, - '澄迈': {'loc': [109.9937,19.7314], 'fullName': '澄迈县'}, - '白沙': {'loc': [109.3703,19.211], 'fullName': '白沙黎族自治县'}, - '琼海': {'loc': [110.4208,19.224], 'fullName': '琼海市'}, - '昌江': {'loc': [109.0407,19.2137], 'fullName': '昌江黎族自治县'}, - '临高': {'loc': [109.6957,19.8063], 'fullName': '临高县'}, - '陵水': {'loc': [109.9924,18.5415], 'fullName': '陵水黎族自治县'}, - '屯昌': {'loc': [110.0377,19.362], 'fullName': '屯昌县'}, - '定安': {'loc': [110.3384,19.4698], 'fullName': '定安县'}, - '保亭': {'loc': [109.6284,18.6108], 'fullName': '保亭黎族苗族自治县'}, - '五指': {'loc': [109.5282,18.8299], 'fullName': '五指山市'} - }; - - // Canvas - this.defaults = {}; - this.defaults.geoDataPath = ""; - this.defaults.width = 600; - this.defaults.height = 500; - this.defaults.mapId = "0"; - this.defaults.showWords = true; // show words or not - this.defaults.levelChangeable = true; // show words or not - this.defaults.colorModel = "discrete"; // discrete or gradient color - //this.defaults.colors = ["#1f77b4", "#ff7f0e"]; - this.defaults.colors = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"]; - this.defaults.defaultAreaColor = "#dddddd"; // if area with no data show this color - this.defaults.wordStyle = {}; - this.defaults.borderStyle = {}; - - /* - this.defaults.fontSizeRatio = 1.0; - */ - - //event - this.defaults.customEvent = { - areaHoverIn : noop, - areaHoverOut : noop, - areaClick: noop, - wordHoverIn: noop, - wordHoverOut: noop, - wordClick: noop - //mousemove : function () {} - }; - this.renderCallback = noop; - - this.setOptions(options); - this.createCanvas(); - } - }); - - /** - * get value from indexes - */ - Chinamap.prototype._searchIndex = function (key, name, regionType) { - var conf = this.defaults; - var map = this; - var result; - var search = function (regionType, name) { - var shortName = name.substr(0, 2); - if (regionType === 'city') { - //prevent duplicate,张家口市,张家界市,阿拉善盟, 阿拉尔市 - if (shortName === '阿拉' || shortName === '张家') { - shortName = name.substr(0, 3); - } - } - - var hash = regionType === 'city' ? map.geoData.cityIndex : map.geoData.provinceIndex; - var result = hash[shortName]; - if (typeof result === 'undefined') { - return undefined; - } - return result[key]; - }; - - if (typeof regionType === 'undefined') { - //province, then city - if (name === '吉林市' || name === '海南藏族自治州') { - //吉林省, 吉林市; 海南省,海南藏族自治州 - result = search("city", name); - } else { - result = search("province", name) || search("city", name); - } - } else { - if (regionType === 'province') { - //province - result = search("province", name); - } else if (regionType === 'city') { - //city - result = search("city", name); - } - } - - return result; - }; - - /** - * get longitude and latitude center by city or porvince name - * regionType is optional, if it's undefined, then first search province, then city - */ - Chinamap.prototype.getLoc = function (name, regionType) { - return this._searchIndex('loc', name, regionType); - }; - - /** - * get longitude and latitude center by porvince name - */ - Chinamap.prototype.getProvinceCenter = function (name) { - return this.getLoc(name, 'province'); - }; - - /** - * get longitude and latitude center by city name - */ - Chinamap.prototype.getCityCenter = function (name) { - return this.getLoc(name, 'city'); - }; - - /** - * get format name by city or porvince name - * regionType is optional, if it's undefined, then first search province, then city - */ - Chinamap.prototype.getFormatName = function (name, regionType) { - return this._searchIndex('fullName', name, regionType); - }; - - /** - * get fullName by porvince name - */ - Chinamap.prototype.getProvinceFormatName = function (name) { - return this.getFormatName(name, 'province'); - }; - - /** - * get fullName by city name - */ - Chinamap.prototype.getCityFormatName = function (name) { - return this.getFormatName(name, 'city'); - }; - - /** - * Create dom node relate to chinamap - */ - Chinamap.prototype.createCanvas = function () { - var conf = this.defaults, - canvasStyle, - container = this.node; - - this.canvas = document.createElement("div"); - canvasStyle = this.canvas.style; - canvasStyle.position = "relative"; - canvasStyle.width = conf.width + "px"; - canvasStyle.height = conf.height + "px"; - container.appendChild(this.canvas); - - this.paper = new Raphael(this.canvas, conf.width, conf.height); - //$(this.node).css("opacity", 0.01); - - //this.node.style.position = "relative"; - this.floatTag = DataV.FloatTag()(this.canvas); - this.floatTag.css({"visibility": "hidden"}); - /* - */ - - }; - - - /** - * 获取颜色 - * Examples: - * ``` - * // 获取第二种颜色的渐变色。 - * {mode: "gradient", index: 1} - * // 获取最深的离散色。 - * {mode: "random", ratio: 0} - * // 获取最浅的离散色。 - * {mode: "random", ratio: 1} - * // 获取适中的离散色。 - * {mode: "random", ratio: 0.5} - * ``` - * @param {Object} colorJson Way to get color from color theme matrix - * @return {Array} 返回颜色数组 - */ - Chinamap.prototype.getColor = function (d) { - var colors = this.defaults.colors; - var value; - if (typeof this.colorGenerator.range === 'undefined') { - if (this.defaults.colorModel === 'discrete') { - this.colorGenerator = d3.scale.ordinal().range(colors); - } else { - this.colorGenerator = d3.scale.linear() - .range(colors.length === 1 ? colors.concat(colors) : colors) - .domain(d3.range(0, 1, 1 / (colors.length - 1)).concat([1])); - } - this.colorGenerator.min = d3.min(d3.values(this.sourceData)); - this.colorGenerator.domainBand = d3.max(d3.values(this.sourceData)) - this.colorGenerator.min; - } - //return this.colorGenerator(d.id); - //return this.colorGenerator(this.sourceData(d.properties.name)); - /* - console.log((parseInt(d.id, 10) % 100) / 50); - console.log(this.colorGenerator((parseInt(d.id, 10) % 100) / 50)); - return this.colorGenerator((parseInt(d.id, 10) % 100) / 50); - */ - value = this.sourceData[d.properties.name]; - if (typeof value === 'undefined') { - //no data area color - return this.defaults.defaultAreaColor; - } else { - if (this.defaults.colorModel === 'discrete') { - return this.colorGenerator(value); - } else { - return this.colorGenerator(this.colorGenerator.domainBand === 0 - ? 1 - : (value - this.colorGenerator.min) / this.colorGenerator.domainBand - ); - } - } - }; - - - /* - * 设置数据源 - * Examples: - * treemap数据输入的格式可以是二维数组。例如下面的数组表示2000年4个季度的天数。 - * 第1季度下面还列出了1-3月的天数。数组的第一行为四个固定的字符串"ID","name","size"和"parentID"。 - * 四列数据分别表示层次数据集中各结点的ID,名称,大小和父节点ID。叶子节点必须有大小,根结点不能有父节点ID。各结点的ID、名称必须要有。 - * ``` - * [ - * ["ID", "name", "size", "parentID"], - * [0, "2000", , ], - * [1, "season1", , 0], - * [2, "January", 31, 1], - * [3, "February", 29, 1], - * [4, "Match", 31, 1], - * [5, "season2", 91, 0], - * [6, "season3", 92, 0], - * [7, "season4", 92, 0] - * ] - * ``` - * 数据还可以是json格式。每个结点都有`name`,如果是父节点则还有`children`,如果为叶节点则还有`size`。以上数组数据对应的json数据如下: - * @param {Array|Object} source json or 2-d array - */ - Chinamap.prototype.setSource = function (source) { - var key, value, formatName; - for (key in source) { - var formatName = this.getFormatName(key); - if (typeof formatName !== 'undefined') { - this.sourceData[formatName] = source[key]; - } - } - /* - if (source instanceof Array) { - this.rawData = this._arrayToJson(source); - } else { - this.rawData = source; - } - this.source = this._remapSource(this.rawData); - this.selectedTreeNodes = [this.source[0]]; - */ - }; - - /*! - * d3 chinamap layout - */ - Chinamap.prototype.layout = function () { - /* - var chinamap = this._createTreemap() - .sort(function (a, b) { return a.value - b.value; }); - this.nodes = chinamap.nodes(this.treeNodeJson); - */ - this.projection = d3.geo.albers() - .origin([105, 30.5]) - .scale(4000); - - this.getAreaPath = d3.geo.path() - .projection(this.projection); - }; - - /*! - * 生成绘制路径 - */ - Chinamap.prototype.generatePaths = function () { - var conf = this.defaults; - var customEvent = conf.customEvent; - var map = this; - var states = map.states; - var words = map.words; - var projection = map.projection; - var getAreaPath = map.getAreaPath; - var mapCache = map.mapCache; - var paper = this.paper; - var areaBoxes = this.geoData.areaBoxes; - - var render = function (areaId, json) { - var getTitle = function (d) { - return d.properties.name; - }; - - var getCallback = function (d) { - return function () { - parseInt(areaId, 10) === 0 - ? (function () { - if (parseInt(d.properties.childNum, 10) === 1) { - return; - } - if (typeof mapCache[d.id] === 'undefined') { - d3.json(conf.geoDataPath + d.id + ".json", function (j) { - render(d.id, j); - }); - } else { - render(d.id); - } - }()) - : (function () { - if (typeof map.mapCache[0] === 'undefined') { - d3.json(conf.geoDataPath + "0.json", function (j) { - render(0, j); - }); - } else { - render(0); - } - }()); - }; - }; - var getCenterX = function (d) { - return projection(d.properties.cp)[0]; - }; - var getCenterY = function (d) { - return projection(d.properties.cp)[1]; - }; - var getText = function (d) { - return d.properties.name; - }; - - //paper.clear(); - states.forEach(function (d, i) { - d.hide(); - }); - words.forEach(function (d, i) { - d.hide(); - }); - - states = map.states = []; - words = map.words = []; - - if (typeof mapCache[areaId] === 'undefined') {//no cache - cache = mapCache[areaId] = { - states: [], - words: [] - }; - - //state - json.features.forEach(function (d, i) { - var state = paper.path(getAreaPath(d)); - d.fillColor = map.getColor(d); - state.attr({ - "fill": d.fillColor, - "stroke": "#fff" - }) - .data("info", d) - .click(customEvent.areaClick) - .mouseover(customEvent.areaHoverIn) - .mouseout(customEvent.areaHoverOut); - if (conf.levelChangeable) { - state.click(getCallback(d)); - } - states.push(state); - state.node.debugName = d.id; - cache.states.push(state); - }); - - //word - json.features.forEach(function (d, i) { - var word = paper.text(getCenterX(d), getCenterY(d), getText(d)); - word.attr({ - "font-family": '"微软雅黑", "宋体"' - }) - .data("info", d) - .click(customEvent.wordClick) - .mouseover(customEvent.wordHoverIn) - .mouseout(customEvent.wordHoverOut); - if (!conf.showWords) { - word.hide(); - } - if (conf.levelChangeable) { - word.click(getCallback(d)); - } - words.push(word); - cache.words.push(word); - }); - - states.forEach(function (d, i) { - d.data("word", words[i]) - .data("map", map); - words[i].data("state", d) - .data("map", map); - }); - - } else {//cached - //state - states = mapCache[areaId].states; - states.forEach(function (d) { - d.show(); - }); - - //word - words = mapCache[areaId].words; - if (conf.showWords) { - words.forEach(function (d) { - d.show(); - }); - } - } - - var getStatesBox = function () { - var box = {}; - var areabox = areaBoxes[areaId]; - box.x = areabox[0]; - box.y = areabox[1]; - box.width = areabox[2]; - box.height = areabox[3]; - box.x2 = areabox[2] + areabox[0]; - box.y2 = areabox[3] + areabox[1]; - return box; - }; - - (function trans () { - var recentViewBox = map.viewBox.slice(); - var statesBox = getStatesBox(); - var newBox = [statesBox.x, statesBox.y, statesBox.width, statesBox.height]; - var scale = Math.max(statesBox.width / conf.width, statesBox.height / conf.height); // the ratio that keep the font be the same size no matter what view box size is. - - var viewBoxAnim = function (oldBox, newBox, time) { - var ti = 30; - var flag = true; - var start = +new Date(); - var getBox = function (ratio) { - var box = []; - var i, l; - if (ratio >= 1) { - return newBox; - } - for (i = 0, l = oldBox.length; i < l; i++) { - box[i] = (newBox[i] - oldBox[i]) * ratio + oldBox[i]; - }; - return box; - }; - var getRatio = function () { - var t = +new Date(); - var ratio = (t - start) / time; - if (ratio > 1) { - ratio = 1; - } - var easing = function (n) { - return Math.pow(n, 1.7); - }; - return easing(ratio); - }; - var anim = function () { - if (flag === false) { - return; - } - //not permit new flame - flag = false; - //set new flame; - var ratio = getRatio(); - var box = getBox(ratio); - //draw - paper.setViewBox(box[0], box[1], box[2], box[3], true); - //console.log(box[0] + " " + box[1] + " " + box[2] + " " + box[3]); - - //clearInterval; permit new flame; - if (ratio >= 1) { - clearInterval(interval); - } - flag = true; - }; - var interval = setInterval(anim, ti); - }; - - states.forEach(function (d, i) { - states[i].attr({ - "stroke-width": 1 //2 * scale - }) - .attr(conf.wordStyle); - words[i].attr({ - "font-size": Math.round(Math.max(14 * scale, 1))//14 * scale + "px" - }) - .attr(conf.borderStyle); - }); - - if (recentViewBox.length === 0) { // first render - paper.setViewBox(newBox[0], newBox[1], newBox[2], newBox[3], true); - $("#chart").css("opacity", 1); - } else { - if (Raphael.vml) { - paper.setViewBox(newBox[0], newBox[1], newBox[2], newBox[3], true); - } else { - viewBoxAnim(recentViewBox, newBox, 750); - } - } - - map.viewBox = newBox; - map.viewBoxShift = (function (x, y, w, h, fit) { - var width = conf.width, - height = conf.height, - size = 1 / Math.max(w / width, h / height), - H, W; - if (fit) { - H = height / h; - W = width / w; - if (w * H < width) { - x -= (width - w * H) / 2 / H; - } - if (h * W < height) { - y -= (height - h * W) / 2 / W; - } - } - return { - dx: -x, - dy: -y, - scale: size - }; - }(newBox[0], newBox[1], newBox[2], newBox[3], true)); - }()); - - map.renderCallback(); - }; - - d3.json(conf.geoDataPath + conf.mapId + ".json", function(json) { - render(conf.mapId, json); - }); - - }; - - /** - * 清除画布 - */ - Chinamap.prototype.clearCanvas = function () { - //this.canvas.innerHTML = ""; - this.paper.clear(); - }; - - /** - * 计算布局位置,并渲染图表 - */ - Chinamap.prototype.render = function (options) { - this.setOptions(options); - this.clearCanvas(); - this.layout(); - this.generatePaths(); - }; - - /** - * 将点在矢量图中的位置 转化为 实际显示点相对于图片左上角的位置(像素距离) - */ - Chinamap.prototype._scaleLocToPixelLoc = function (scaleLoc) { - var map = this; - var scale = map.viewBoxShift.scale; - var viewCenter = { - 'x': map.viewBox[0] + map.viewBox[2] / 2, - 'y': map.viewBox[1] + map.viewBox[3] / 2 - }; - return { - 'x': (scaleLoc.x - viewCenter.x) * scale + map.defaults.width / 2, - 'y': (scaleLoc.y - viewCenter.y) * scale + map.defaults.height / 2 - }; - }; - - /** - * 将实际显示点相对于图片左上角的位置(像素距离) 转化为 点在矢量图中的位置 - */ - Chinamap.prototype._pixelLocToScaleLoc = function (pixelLoc) { - var map = this; - var scale = map.viewBoxShift.scale; - var viewCenter = { - 'x': map.viewBox[0] + map.viewBox[2] / 2, - 'y': map.viewBox[1] + map.viewBox[3] / 2 - }; - return { - 'x': (pixelLoc.x - map.defaults.width / 2) / scale + viewCenter.x, - 'y': (pixelLoc.y - map.defaults.height / 2) / scale + viewCenter.y - }; - }; - - /** - * 渲染城市点 - */ - Chinamap.prototype.createCityPoints = function (cities, callback) { - var conf = this.defaults; - var map = this; - var point; - var points = []; - var cb = callback || function (city) { - return this.paper.circle(city.coord[0], city.coord[1], 20) - .attr({ - "fill": "steelblue", - "fill-opacity": 0.5 - }); - }; - - cities.forEach(function (d) { - var viewCenter = { - 'x': map.viewBox[0] + map.viewBox[2] / 2, - 'y': map.viewBox[1] + map.viewBox[3] / 2 - }; - //get format name - var formatName = map.getCityFormatName(d.name); - if (typeof formatName === 'undefined') { - if (typeof d.lanlon === 'undefined') { - return; - } else { - d.formatName = d.name; - } - } else { - d.formatName = formatName; - //get loc (lan, lon), use user provided lanlon by default; - d.lanlon = d.lanlon || map.getCityCenter(d.formatName); - } - //process loc (geo projection) - d.coord = map.projection(d.lanlon); - //x and y of circle center in the container; - d.pointLoc = map._scaleLocToPixelLoc({ - 'x': d.coord[0], - 'y': d.coord[1] - }); - //callback - point = cb.call(map, d); - points.push(point); - }); - return points; - }; - - /** - * 设置自定义事件 - */ - Chinamap.prototype.setCustomEvent = function (eventName, callback) { - if ($.inArray(eventName, ["areaHoverIn", "areaHoverOut", "areaClick", - "wordHoverIn", "wordHoverOut", "wordClick"]) !== -1) { - this.defaults.customEvent[eventName] = callback; - } - }; - - /*! - * 导出Chinamap - */ - return Chinamap; -}); +/*global d3, $, define */ +/*! + * Chinamap兼容定义部分 + */ +;(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];}); + } +})('Chinamap', function (require) { + var DataV = require('DataV'); + + /* + * Chinamap构造函数,继承自Chart + * Options: + * + * - `width` 数字,图片宽度,默认为750,表示图片高750px + * - `height` 数字,图片高度,默认为500 + * - `showBackTag` 布尔值,回退操作导航条是否显示,默认为 true, 显示;设为false则不显示 + * - `backHeight` 数字,回退操作导航条宽度,默认为20 + * - `level1BorderWidth` 数字,一级方框的边框宽度,默认为1(1px),不建议修改 + * - `level2BorderWidth` 数字,二级方框的边框宽度,默认为1(1px),不建议修改 + * - `fontSizeRatio` 数字,表示图中文字大小。默认为1.0(1倍), 若设为2.0,字体大小会加倍; + * - `customEvent` 函数对象,其中有4个自定义函数。`leafNodeClick` 函数,表示点击叶子节点的事件响应,默认为空函数; `hoverIn` 函数,表示鼠标移进方框的事件响应,默认为空函数; `hoverOut` 函数,表示鼠标移出方框的事件响应,默认为空函数; `mouseover` 函数,表示在方框内移动鼠标的事件响应,默认为设置浮框的内容,可以替换它修改浮框内容; 这些函数可以在创建对象或setOption()时一起设置,也可以通过on函数单独设置。 + * + * Examples: + * create chinamap in a dom node with id "chart", width is 500; height is 600px; + * ``` + * var chinamap = new Chinamap("chart", {"width": 500, "height": 600}); + * ``` + * @param {Object} node The dom node or dom node Id + * @param {Object} options JSON object for determin chinamap style + */ + var Chinamap = DataV.extend(DataV.Chart, { + initialize: function (node, options) { + var noop = function () {}; + this.type = "Chinamap"; + this.node = this.checkContainer(node); + + // Properties + /* + this.floatTag;//浮框对象,这是个可操作的对象。 + */ + this.viewBox = []; + this.mapCache = {}; + this.states = []; + this.words = []; + this.projection = function () {}; // d3 map prejection function + this.getAreaPath = function () {}; // input geojson feature, return area path string + this.colorGenerator = function () {}; // produce gradient or discrete color; + this.sourceData = {}; + this.geoData = {}; + this.geoData.areaIdIndex = { + '全国': 0, + '新疆': 65, + '西藏': 54, + '内蒙古': 15, + '青海': 63, + '四川': 51, + '黑龙江': 23, + '甘肃': 62, + '云南': 53, + '广西': 45, + '湖南': 43, + '陕西': 61, + '广东': 44, + '吉林': 22, + '河北': 13, + '湖北': 42, + '贵州': 52, + '山东': 37, + '江西': 36, + '河南': 41, + '辽宁': 21, + '山西': 14, + '安徽': 34, + '福建': 35, + '浙江': 33, + '江苏': 32, + '重庆': 50, + '宁夏': 64, + '海南': 46, + '台湾': 71, + '北京': 11, + '天津': 12, + '上海': 31, + '香港': 81, + '澳门': 82 + }; + this.geoData.areaBoxes = { + //x, y, width, height when projection scale is 4000 + 0: [-1174.6445229087194, -1437.3577680805693, 3039.3970214233723, 2531.19589698184], + 65: [-1174.9404317915883, -1136.0130934711678, 1216.4169237052663, 939.4360818385251], + 54: [-1061.2905098655508, -273.40253896102865, 1182.4138890465167, 728.4762434212385], + 15: [81.92106433333947, -1404.5655158641246, 1337.913665139638, 1168.7030286278964], + 63: [-398.0407413665446, -404.86540158240564, 770.5429460357634, 553.4881569694239], + 51: [34.77351011413543, -24.727858097581816, 654.265749584143, 581.5837904142871], + 23: [1185.0861642873883, -1435.9087566254907, 680.9449423479143, 618.3772597960831], + 62: [-197.5222870378875, -631.2015222269291, 884.6861134736321, 734.2542202456989], + 53: [-4.030270169151834, 326.89754492870105, 561.4971786143803, 565.9079094851168], + 45: [444.4355364538484, 524.7911424174906, 490.6548359068431, 384.1667316158848], + 43: [716.7125751678784, 265.3988842488122, 346.1702652872375, 377.50144051998274], + 61: [508.5948583446903, -399.56997062473215, 321.038690321553, 559.1002147021181], + 44: [790.2032875493967, 572.9640361040085, 494.8279567104971, 388.7112686526252], + 22: [1287.5729431804648, -950.943295028444, 504.33243011403374, 354.162667814153], + 13: [940.0156020671719, -646.4007207319194, 325.33903805510784, 477.4542727272415], + 42: [683.8325394595918, 45.82949601748078, 468.66717545627034, 295.2142095820616], + 52: [392.5021834497175, 337.4483828727408, 375.50579966539516, 320.9420464446699], + 37: [1035.7855473594757, -382.19242168799906, 412.5747391303373, 313.152767793266], + 36: [1012.6841751377355, 236.50140310944056, 295.599802392515, 400.86430917822287], + 41: [785.5419798731749, -185.2911232263814, 362.6977821251186, 340.3902676066224], + 21: [1203.0641741691293, -757.0946871553339, 352.71788824534656, 357.71276541155214], + 14: [776.5185040689469, -493.6204506126494, 212.68572802329425, 448.08485211774945], + 34: [1054.014965660052, -80.43770626104327, 295.73127466484925, 352.03731065611606], + 35: [1172.0955040211252, 341.81292779438445, 288.99462739279807, 339.42845011348845], + 33: [1272.1789620983063, 123.46272678646208, 286.17816622252326, 286.73860446060394], + 32: [1125.161343490302, -134.97368204682834, 356.1806346879009, 291.4961628010442], + 50: [497.78832088614774, 127.0051229616378, 291.91221530072164, 280.8880182020781], + 64: [441.193675072408, -376.31946967355213, 183.76989823787306, 293.0024551112753], + 46: [723.8031601361929, 946.050886515855, 183.33374783084207, 147.66048518654895], + 71: [1459.925544038912, 519.7445429876257, 103.06085087505835, 237.80851484008463], + 11: [1031.6052083127613, -530.1928574952913, 103.23943439987329, 114.66079087790081], + 12: [1106.9649995752443, -479.16508616378724, 71.21176554916747, 120.01987096046025], + 31: [1420.334836525578, 71.79837578328207, 70.41721601016525, 81.99461244072737], + 81: [1061.983645387268, 769.0837862603122, 50.65584483626753, 32.17422147262721], + 82: [1043.1350056914507, 798.0786255550063, 5.387452843479423, 7.564113979470676] + }; + this.geoData.provinceIndex = { + '新疆': {'loc': [84.9023,41.748], 'fullName': '新疆'}, + '西藏': {'loc': [88.7695,31.6846], 'fullName': '西藏'}, + '内蒙': {'loc': [117.5977,44.3408], 'fullName': '内蒙古'}, + '青海': {'loc': [96.2402,35.4199], 'fullName': '青海'}, + '四川': {'loc': [102.9199,30.1904], 'fullName': '四川'}, + '黑龙': {'loc': [128.1445,48.5156], 'fullName': '黑龙江'}, + '甘肃': {'loc': [95.7129,40.166], 'fullName': '甘肃'}, + '云南': {'loc': [101.8652,25.1807], 'fullName': '云南'}, + '广西': {'loc': [108.2813,23.6426], 'fullName': '广西'}, + '湖南': {'loc': [111.5332,27.3779], 'fullName': '湖南'}, + '陕西': {'loc': [109.5996,35.6396], 'fullName': '陕西'}, + '广东': {'loc': [113.4668,22.8076], 'fullName': '广东'}, + '吉林': {'loc': [126.4746,43.5938], 'fullName': '吉林'}, + '河北': {'loc': [115.4004,37.9688], 'fullName': '河北'}, + '湖北': {'loc': [112.2363,31.1572], 'fullName': '湖北'}, + '贵州': {'loc': [106.6113,26.9385], 'fullName': '贵州'}, + '山东': {'loc': [118.7402,36.4307], 'fullName': '山东'}, + '江西': {'loc': [116.0156,27.29], 'fullName': '江西'}, + '河南': {'loc': [113.4668,33.8818], 'fullName': '河南'}, + '辽宁': {'loc': [122.3438,41.0889], 'fullName': '辽宁'}, + '山西': {'loc': [112.4121,37.6611], 'fullName': '山西'}, + '安徽': {'loc': [117.2461,32.0361], 'fullName': '安徽'}, + '福建': {'loc': [118.3008,25.9277], 'fullName': '福建'}, + '浙江': {'loc': [120.498,29.0918], 'fullName': '浙江'}, + '江苏': {'loc': [120.0586,32.915], 'fullName': '江苏'}, + '重庆': {'loc': [107.7539,30.1904], 'fullName': '重庆'}, + '宁夏': {'loc': [105.9961,37.3096], 'fullName': '宁夏'}, + '海南': {'loc': [109.9512,19.2041], 'fullName': '海南'}, + '台湾': {'loc': [121.0254,23.5986], 'fullName': '台湾'}, + '北京': {'loc': [116.4551,40.2539], 'fullName': '北京'}, + '天津': {'loc': [117.4219,39.4189], 'fullName': '天津'}, + '上海': {'loc': [121.4648,31.2891], 'fullName': '上海'}, + '香港': {'loc': [114.2578,22.3242], 'fullName': '香港'}, + '澳门': {'loc': [113.5547,22.1484], 'fullName': '澳门'} + }; + this.geoData.cityIndex = { + '重庆': {'loc': [107.7539,30.1904], 'fullName': '重庆'}, + '北京': {'loc': [116.4551,40.2539], 'fullName': '北京'}, + '天津': {'loc': [117.4219,39.4189], 'fullName': '天津'}, + '上海': {'loc': [121.4648,31.2891], 'fullName': '上海'}, + '香港': {'loc': [114.2578,22.3242], 'fullName': '香港'}, + '澳门': {'loc': [113.5547,22.1484], 'fullName': '澳门'}, + '巴音': {'loc': [88.1653,39.6002], 'fullName': '巴音郭楞蒙古自治州'}, + '和田': {'loc': [81.167,36.9855], 'fullName': '和田地区'}, + '哈密': {'loc': [93.7793,42.9236], 'fullName': '哈密地区'}, + '阿克': {'loc': [82.9797,41.0229], 'fullName': '阿克苏地区'}, + '阿勒': {'loc': [88.2971,47.0929], 'fullName': '阿勒泰地区'}, + '喀什': {'loc': [77.168,37.8534], 'fullName': '喀什地区'}, + '塔城': {'loc': [86.6272,45.8514], 'fullName': '塔城地区'}, + '昌吉': {'loc': [89.6814,44.4507], 'fullName': '昌吉回族自治州'}, + '克孜': {'loc': [74.6301,39.5233], 'fullName': '克孜勒苏柯尔克孜自治州'}, + '吐鲁': {'loc': [89.6375,42.4127], 'fullName': '吐鲁番地区'}, + '伊犁': {'loc': [82.5513,43.5498], 'fullName': '伊犁哈萨克自治州'}, + '博尔': {'loc': [81.8481,44.6979], 'fullName': '博尔塔拉蒙古自治州'}, + '乌鲁': {'loc': [87.9236,43.5883], 'fullName': '乌鲁木齐市'}, + '克拉': {'loc': [85.2869,45.5054], 'fullName': '克拉玛依市'}, + '阿拉尔': {'loc': [81.2769,40.6549], 'fullName': '阿拉尔市'}, + '图木': {'loc': [79.1345,39.8749], 'fullName': '图木舒克市'}, + '五家': {'loc': [87.5391,44.3024], 'fullName': '五家渠市'}, + '石河': {'loc': [86.0229,44.2914], 'fullName': '石河子市'}, + '那曲': {'loc': [88.1982,33.3215], 'fullName': '那曲地区'}, + '阿里': {'loc': [82.3645,32.7667], 'fullName': '阿里地区'}, + '日喀': {'loc': [86.2427,29.5093], 'fullName': '日喀则地区'}, + '林芝': {'loc': [95.4602,29.1138], 'fullName': '林芝地区'}, + '昌都': {'loc': [97.0203,30.7068], 'fullName': '昌都地区'}, + '山南': {'loc': [92.2083,28.3392], 'fullName': '山南地区'}, + '拉萨': {'loc': [91.1865,30.1465], 'fullName': '拉萨市'}, + '呼伦': {'loc': [120.8057,50.2185], 'fullName': '呼伦贝尔市'}, + '阿拉善': {'loc': [102.019,40.1001], 'fullName': '阿拉善盟'}, + '锡林': {'loc': [115.6421,44.176], 'fullName': '锡林郭勒盟'}, + '鄂尔': {'loc': [108.9734,39.2487], 'fullName': '鄂尔多斯市'}, + '赤峰': {'loc': [118.6743,43.2642], 'fullName': '赤峰市'}, + '巴彦': {'loc': [107.5562,41.3196], 'fullName': '巴彦淖尔市'}, + '通辽': {'loc': [121.4758,43.9673], 'fullName': '通辽市'}, + '乌兰': {'loc': [112.5769,41.77], 'fullName': '乌兰察布市'}, + '兴安': {'loc': [121.3879,46.1426], 'fullName': '兴安盟'}, + '包头': {'loc': [110.3467,41.4899], 'fullName': '包头市'}, + '呼和': {'loc': [111.4124,40.4901], 'fullName': '呼和浩特市'}, + '乌海': {'loc': [106.886,39.4739], 'fullName': '乌海市'}, + '海西': {'loc': [94.9768,37.1118], 'fullName': '海西蒙古族藏族自治州'}, + '玉树': {'loc': [93.5925,33.9368], 'fullName': '玉树藏族自治州'}, + '果洛': {'loc': [99.3823,34.0466], 'fullName': '果洛藏族自治州'}, + '海南': {'loc': [100.3711,35.9418], 'fullName': '海南藏族自治州'}, + '海北': {'loc': [100.3711,37.9138], 'fullName': '海北藏族自治州'}, + '黄南': {'loc': [101.5686,35.1178], 'fullName': '黄南藏族自治州'}, + '海东': {'loc': [102.3706,36.2988], 'fullName': '海东地区'}, + '西宁': {'loc': [101.4038,36.8207], 'fullName': '西宁市'}, + '甘孜': {'loc': [99.9207,31.0803], 'fullName': '甘孜藏族自治州'}, + '阿坝': {'loc': [102.4805,32.4536], 'fullName': '阿坝藏族羌族自治州'}, + '凉山': {'loc': [101.9641,27.6746], 'fullName': '凉山彝族自治州'}, + '绵阳': {'loc': [104.7327,31.8713], 'fullName': '绵阳市'}, + '达州': {'loc': [107.6111,31.333], 'fullName': '达州市'}, + '广元': {'loc': [105.6885,32.2284], 'fullName': '广元市'}, + '雅安': {'loc': [102.6672,29.8938], 'fullName': '雅安市'}, + '宜宾': {'loc': [104.6558,28.548], 'fullName': '宜宾市'}, + '乐山': {'loc': [103.5791,29.1742], 'fullName': '乐山市'}, + '南充': {'loc': [106.2048,31.1517], 'fullName': '南充市'}, + '巴中': {'loc': [107.0618,31.9977], 'fullName': '巴中市'}, + '泸州': {'loc': [105.4578,28.493], 'fullName': '泸州市'}, + '成都': {'loc': [103.9526,30.7617], 'fullName': '成都市'}, + '资阳': {'loc': [104.9744,30.1575], 'fullName': '资阳市'}, + '攀枝': {'loc': [101.6895,26.7133], 'fullName': '攀枝花市'}, + '眉山': {'loc': [103.8098,30.0146], 'fullName': '眉山市'}, + '广安': {'loc': [106.6333,30.4376], 'fullName': '广安市'}, + '德阳': {'loc': [104.48,31.1133], 'fullName': '德阳市'}, + '内江': {'loc': [104.8535,29.6136], 'fullName': '内江市'}, + '遂宁': {'loc': [105.5347,30.6683], 'fullName': '遂宁市'}, + '自贡': {'loc': [104.6667,29.2786], 'fullName': '自贡市'}, + '黑河': {'loc': [127.1448,49.2957], 'fullName': '黑河市'}, + '大兴': {'loc': [124.1016,52.2345], 'fullName': '大兴安岭地区'}, + '哈尔': {'loc': [127.9688,45.368], 'fullName': '哈尔滨市'}, + '齐齐': {'loc': [124.541,47.5818], 'fullName': '齐齐哈尔市'}, + '牡丹': {'loc': [129.7815,44.7089], 'fullName': '牡丹江市'}, + '绥化': {'loc': [126.7163,46.8018], 'fullName': '绥化市'}, + '伊春': {'loc': [129.1992,47.9608], 'fullName': '伊春市'}, + '佳木': {'loc': [133.0005,47.5763], 'fullName': '佳木斯市'}, + '鸡西': {'loc': [132.7917,45.7361], 'fullName': '鸡西市'}, + '双鸭': {'loc': [133.5938,46.7523], 'fullName': '双鸭山市'}, + '大庆': {'loc': [124.7717,46.4282], 'fullName': '大庆市'}, + '鹤岗': {'loc': [130.4407,47.7081], 'fullName': '鹤岗市'}, + '七台': {'loc': [131.2756,45.9558], 'fullName': '七台河市'}, + '酒泉': {'loc': [96.2622,40.4517], 'fullName': '酒泉市'}, + '张掖': {'loc': [99.7998,38.7433], 'fullName': '张掖市'}, + '甘南': {'loc': [102.9199,34.6893], 'fullName': '甘南藏族自治州'}, + '武威': {'loc': [103.0188,38.1061], 'fullName': '武威市'}, + '陇南': {'loc': [105.304,33.5632], 'fullName': '陇南市'}, + '庆阳': {'loc': [107.5342,36.2], 'fullName': '庆阳市'}, + '白银': {'loc': [104.8645,36.5076], 'fullName': '白银市'}, + '定西': {'loc': [104.5569,35.0848], 'fullName': '定西市'}, + '天水': {'loc': [105.6445,34.6289], 'fullName': '天水市'}, + '兰州': {'loc': [103.5901,36.3043], 'fullName': '兰州市'}, + '平凉': {'loc': [107.0728,35.321], 'fullName': '平凉市'}, + '临夏': {'loc': [103.2715,35.5737], 'fullName': '临夏回族自治州'}, + '金昌': {'loc': [102.074,38.5126], 'fullName': '金昌市'}, + '嘉峪': {'loc': [98.1738,39.8035], 'fullName': '嘉峪关市'}, + '普洱': {'loc': [100.7446,23.4229], 'fullName': '普洱市'}, + '红河': {'loc': [103.0408,23.6041], 'fullName': '红河哈尼族彝族自治州'}, + '文山': {'loc': [104.8865,23.5712], 'fullName': '文山壮族苗族自治州'}, + '曲靖': {'loc': [103.9417,25.7025], 'fullName': '曲靖市'}, + '楚雄': {'loc': [101.6016,25.3619], 'fullName': '楚雄彝族自治州'}, + '大理': {'loc': [99.9536,25.6805], 'fullName': '大理白族自治州'}, + '临沧': {'loc': [99.613,24.0546], 'fullName': '临沧市'}, + '迪庆': {'loc': [99.4592,27.9327], 'fullName': '迪庆藏族自治州'}, + '昭通': {'loc': [104.0955,27.6031], 'fullName': '昭通市'}, + '昆明': {'loc': [102.9199,25.4663], 'fullName': '昆明市'}, + '丽江': {'loc': [100.448,26.955], 'fullName': '丽江市'}, + '西双': {'loc': [100.8984,21.8628], 'fullName': '西双版纳傣族自治州'}, + '保山': {'loc': [99.0637,24.9884], 'fullName': '保山市'}, + '玉溪': {'loc': [101.9312,23.8898], 'fullName': '玉溪市'}, + '怒江': {'loc': [99.1516,26.5594], 'fullName': '怒江傈僳族自治州'}, + '德宏': {'loc': [98.1299,24.5874], 'fullName': '德宏傣族景颇族自治州'}, + '百色': {'loc': [106.6003,23.9227], 'fullName': '百色市'}, + '河池': {'loc': [107.8638,24.5819], 'fullName': '河池市'}, + '桂林': {'loc': [110.5554,25.318], 'fullName': '桂林市'}, + '南宁': {'loc': [108.479,23.1152], 'fullName': '南宁市'}, + '柳州': {'loc': [109.3799,24.9774], 'fullName': '柳州市'}, + '崇左': {'loc': [107.3364,22.4725], 'fullName': '崇左市'}, + '来宾': {'loc': [109.7095,23.8403], 'fullName': '来宾市'}, + '玉林': {'loc': [110.2148,22.3792], 'fullName': '玉林市'}, + '梧州': {'loc': [110.9949,23.5052], 'fullName': '梧州市'}, + '贺州': {'loc': [111.3135,24.4006], 'fullName': '贺州市'}, + '钦州': {'loc': [109.0283,22.0935], 'fullName': '钦州市'}, + '贵港': {'loc': [109.9402,23.3459], 'fullName': '贵港市'}, + '防城': {'loc': [108.0505,21.9287], 'fullName': '防城港市'}, + '北海': {'loc': [109.314,21.6211], 'fullName': '北海市'}, + '怀化': {'loc': [109.9512,27.4438], 'fullName': '怀化市'}, + '永州': {'loc': [111.709,25.752], 'fullName': '永州市'}, + '邵阳': {'loc': [110.9619,26.8121], 'fullName': '邵阳市'}, + '郴州': {'loc': [113.2361,25.8673], 'fullName': '郴州市'}, + '常德': {'loc': [111.4014,29.2676], 'fullName': '常德市'}, + '湘西': {'loc': [109.7864,28.6743], 'fullName': '湘西土家族苗族自治州'}, + '衡阳': {'loc': [112.4121,26.7902], 'fullName': '衡阳市'}, + '岳阳': {'loc': [113.2361,29.1357], 'fullName': '岳阳市'}, + '益阳': {'loc': [111.731,28.3832], 'fullName': '益阳市'}, + '长沙': {'loc': [113.0823,28.2568], 'fullName': '长沙市'}, + '株洲': {'loc': [113.5327,27.0319], 'fullName': '株洲市'}, + '张家界': {'loc': [110.5115,29.328], 'fullName': '张家界市'}, + '娄底': {'loc': [111.6431,27.7185], 'fullName': '娄底市'}, + '湘潭': {'loc': [112.5439,27.7075], 'fullName': '湘潭市'}, + '榆林': {'loc': [109.8743,38.205], 'fullName': '榆林市'}, + '延安': {'loc': [109.1052,36.4252], 'fullName': '延安市'}, + '汉中': {'loc': [106.886,33.0139], 'fullName': '汉中市'}, + '安康': {'loc': [109.1162,32.7722], 'fullName': '安康市'}, + '商洛': {'loc': [109.8083,33.761], 'fullName': '商洛市'}, + '宝鸡': {'loc': [107.1826,34.3433], 'fullName': '宝鸡市'}, + '渭南': {'loc': [109.7864,35.0299], 'fullName': '渭南市'}, + '咸阳': {'loc': [108.4131,34.8706], 'fullName': '咸阳市'}, + '西安': {'loc': [109.1162,34.2004], 'fullName': '西安市'}, + '铜川': {'loc': [109.0393,35.1947], 'fullName': '铜川市'}, + '清远': {'loc': [112.9175,24.3292], 'fullName': '清远市'}, + '韶关': {'loc': [113.7964,24.7028], 'fullName': '韶关市'}, + '湛江': {'loc': [110.3577,20.9894], 'fullName': '湛江市'}, + '梅州': {'loc': [116.1255,24.1534], 'fullName': '梅州市'}, + '河源': {'loc': [114.917,23.9722], 'fullName': '河源市'}, + '肇庆': {'loc': [112.1265,23.5822], 'fullName': '肇庆市'}, + '惠州': {'loc': [114.6204,23.1647], 'fullName': '惠州市'}, + '茂名': {'loc': [111.0059,22.0221], 'fullName': '茂名市'}, + '江门': {'loc': [112.6318,22.1484], 'fullName': '江门市'}, + '阳江': {'loc': [111.8298,22.0715], 'fullName': '阳江市'}, + '云浮': {'loc': [111.7859,22.8516], 'fullName': '云浮市'}, + '广州': {'loc': [113.5107,23.2196], 'fullName': '广州市'}, + '汕尾': {'loc': [115.5762,23.0438], 'fullName': '汕尾市'}, + '揭阳': {'loc': [116.1255,23.313], 'fullName': '揭阳市'}, + '珠海': {'loc': [113.7305,22.1155], 'fullName': '珠海市'}, + '佛山': {'loc': [112.8955,23.1097], 'fullName': '佛山市'}, + '潮州': {'loc': [116.7847,23.8293], 'fullName': '潮州市'}, + '汕头': {'loc': [117.1692,23.3405], 'fullName': '汕头市'}, + '深圳': {'loc': [114.5435,22.5439], 'fullName': '深圳市'}, + '东莞': {'loc': [113.8953,22.901], 'fullName': '东莞市'}, + '中山': {'loc': [113.4229,22.478], 'fullName': '中山市'}, + '延边': {'loc': [129.397,43.2587], 'fullName': '延边朝鲜族自治州'}, + '吉林': {'loc': [126.8372,43.6047], 'fullName': '吉林市'}, + '白城': {'loc': [123.0029,45.2637], 'fullName': '白城市'}, + '松原': {'loc': [124.0906,44.7198], 'fullName': '松原市'}, + '长春': {'loc': [125.8154,44.2584], 'fullName': '长春市'}, + '白山': {'loc': [127.2217,42.0941], 'fullName': '白山市'}, + '通化': {'loc': [125.9583,41.8579], 'fullName': '通化市'}, + '四平': {'loc': [124.541,43.4894], 'fullName': '四平市'}, + '辽源': {'loc': [125.343,42.7643], 'fullName': '辽源市'}, + '承德': {'loc': [117.5757,41.4075], 'fullName': '承德市'}, + '张家口': {'loc': [115.1477,40.8527], 'fullName': '张家口市'}, + '保定': {'loc': [115.0488,39.0948], 'fullName': '保定市'}, + '唐山': {'loc': [118.4766,39.6826], 'fullName': '唐山市'}, + '沧州': {'loc': [116.8286,38.2104], 'fullName': '沧州市'}, + '石家': {'loc': [114.4995,38.1006], 'fullName': '石家庄市'}, + '邢台': {'loc': [114.8071,37.2821], 'fullName': '邢台市'}, + '邯郸': {'loc': [114.4775,36.535], 'fullName': '邯郸市'}, + '秦皇': {'loc': [119.2126,40.0232], 'fullName': '秦皇岛市'}, + '衡水': {'loc': [115.8838,37.7161], 'fullName': '衡水市'}, + '廊坊': {'loc': [116.521,39.0509], 'fullName': '廊坊市'}, + '恩施': {'loc': [109.5007,30.2563], 'fullName': '恩施土家族苗族自治州'}, + '十堰': {'loc': [110.5115,32.3877], 'fullName': '十堰市'}, + '宜昌': {'loc': [111.1707,30.7617], 'fullName': '宜昌市'}, + '襄樊': {'loc': [111.9397,31.9263], 'fullName': '襄樊市'}, + '黄冈': {'loc': [115.2686,30.6628], 'fullName': '黄冈市'}, + '荆州': {'loc': [113.291,30.0092], 'fullName': '荆州市'}, + '荆门': {'loc': [112.6758,30.9979], 'fullName': '荆门市'}, + '咸宁': {'loc': [114.2578,29.6631], 'fullName': '咸宁市'}, + '随州': {'loc': [113.4338,31.8768], 'fullName': '随州市'}, + '孝感': {'loc': [113.9502,31.1188], 'fullName': '孝感市'}, + '武汉': {'loc': [114.3896,30.6628], 'fullName': '武汉市'}, + '黄石': {'loc': [115.0159,29.9213], 'fullName': '黄石市'}, + '神农': {'loc': [110.4565,31.5802], 'fullName': '神农架林区'}, + '天门': {'loc': [113.0273,30.6409], 'fullName': '天门市'}, + '仙桃': {'loc': [113.3789,30.3003], 'fullName': '仙桃市'}, + '潜江': {'loc': [112.7637,30.3607], 'fullName': '潜江市'}, + '鄂州': {'loc': [114.7302,30.4102], 'fullName': '鄂州市'}, + '遵义': {'loc': [106.908,28.1744], 'fullName': '遵义市'}, + '黔东': {'loc': [108.4241,26.4166], 'fullName': '黔东南苗族侗族自治州'}, + '毕节': {'loc': [105.1611,27.0648], 'fullName': '毕节地区'}, + '黔南': {'loc': [107.2485,25.8398], 'fullName': '黔南布依族苗族自治州'}, + '铜仁': {'loc': [108.6218,28.0096], 'fullName': '铜仁地区'}, + '黔西': {'loc': [105.5347,25.3949], 'fullName': '黔西南布依族苗族自治州'}, + '六盘': {'loc': [104.7546,26.0925], 'fullName': '六盘水市'}, + '安顺': {'loc': [105.9082,25.9882], 'fullName': '安顺市'}, + '贵阳': {'loc': [106.6992,26.7682], 'fullName': '贵阳市'}, + '烟台': {'loc': [120.7397,37.5128], 'fullName': '烟台市'}, + '临沂': {'loc': [118.3118,35.2936], 'fullName': '临沂市'}, + '潍坊': {'loc': [119.0918,36.524], 'fullName': '潍坊市'}, + '青岛': {'loc': [120.4651,36.3373], 'fullName': '青岛市'}, + '菏泽': {'loc': [115.6201,35.2057], 'fullName': '菏泽市'}, + '济宁': {'loc': [116.8286,35.3375], 'fullName': '济宁市'}, + '德州': {'loc': [116.6858,37.2107], 'fullName': '德州市'}, + '滨州': {'loc': [117.8174,37.4963], 'fullName': '滨州市'}, + '聊城': {'loc': [115.9167,36.4032], 'fullName': '聊城市'}, + '东营': {'loc': [118.7073,37.5513], 'fullName': '东营市'}, + '济南': {'loc': [117.1582,36.8701], 'fullName': '济南市'}, + '泰安': {'loc': [117.0264,36.0516], 'fullName': '泰安市'}, + '威海': {'loc': [121.9482,37.1393], 'fullName': '威海市'}, + '日照': {'loc': [119.2786,35.5023], 'fullName': '日照市'}, + '淄博': {'loc': [118.0371,36.6064], 'fullName': '淄博市'}, + '枣庄': {'loc': [117.323,34.8926], 'fullName': '枣庄市'}, + '莱芜': {'loc': [117.6526,36.2714], 'fullName': '莱芜市'}, + '赣州': {'loc': [115.2795,25.8124], 'fullName': '赣州市'}, + '吉安': {'loc': [114.884,26.9659], 'fullName': '吉安市'}, + '上饶': {'loc': [117.8613,28.7292], 'fullName': '上饶市'}, + '九江': {'loc': [115.4224,29.3774], 'fullName': '九江市'}, + '抚州': {'loc': [116.4441,27.4933], 'fullName': '抚州市'}, + '宜春': {'loc': [115.0159,28.3228], 'fullName': '宜春市'}, + '南昌': {'loc': [116.0046,28.6633], 'fullName': '南昌市'}, + '景德': {'loc': [117.334,29.3225], 'fullName': '景德镇市'}, + '萍乡': {'loc': [113.9282,27.4823], 'fullName': '萍乡市'}, + '鹰潭': {'loc': [117.0813,28.2349], 'fullName': '鹰潭市'}, + '新余': {'loc': [114.95,27.8174], 'fullName': '新余市'}, + '南阳': {'loc': [112.4011,33.0359], 'fullName': '南阳市'}, + '信阳': {'loc': [114.8291,32.0197], 'fullName': '信阳市'}, + '洛阳': {'loc': [112.0605,34.3158], 'fullName': '洛阳市'}, + '驻马': {'loc': [114.1589,32.9041], 'fullName': '驻马店市'}, + '周口': {'loc': [114.873,33.6951], 'fullName': '周口市'}, + '商丘': {'loc': [115.741,34.2828], 'fullName': '商丘市'}, + '三门': {'loc': [110.8301,34.3158], 'fullName': '三门峡市'}, + '新乡': {'loc': [114.2029,35.3595], 'fullName': '新乡市'}, + '平顶': {'loc': [112.9724,33.739], 'fullName': '平顶山市'}, + '郑州': {'loc': [113.4668,34.6234], 'fullName': '郑州市'}, + '安阳': {'loc': [114.5325,36.0022], 'fullName': '安阳市'}, + '开封': {'loc': [114.5764,34.6124], 'fullName': '开封市'}, + '焦作': {'loc': [112.8406,35.1508], 'fullName': '焦作市'}, + '许昌': {'loc': [113.6975,34.0466], 'fullName': '许昌市'}, + '濮阳': {'loc': [115.1917,35.799], 'fullName': '濮阳市'}, + '漯河': {'loc': [113.8733,33.6951], 'fullName': '漯河市'}, + '鹤壁': {'loc': [114.3787,35.744], 'fullName': '鹤壁市'}, + '大连': {'loc': [122.2229,39.4409], 'fullName': '大连市'}, + '朝阳': {'loc': [120.0696,41.4899], 'fullName': '朝阳市'}, + '丹东': {'loc': [124.541,40.4242], 'fullName': '丹东市'}, + '铁岭': {'loc': [124.2773,42.7423], 'fullName': '铁岭市'}, + '沈阳': {'loc': [123.1238,42.1216], 'fullName': '沈阳市'}, + '抚顺': {'loc': [124.585,41.8579], 'fullName': '抚顺市'}, + '葫芦': {'loc': [120.1575,40.578], 'fullName': '葫芦岛市'}, + '阜新': {'loc': [122.0032,42.2699], 'fullName': '阜新市'}, + '锦州': {'loc': [121.6626,41.4294], 'fullName': '锦州市'}, + '鞍山': {'loc': [123.0798,40.6055], 'fullName': '鞍山市'}, + '本溪': {'loc': [124.1455,41.1987], 'fullName': '本溪市'}, + '营口': {'loc': [122.4316,40.4297], 'fullName': '营口市'}, + '辽阳': {'loc': [123.4094,41.1383], 'fullName': '辽阳市'}, + '盘锦': {'loc': [121.9482,41.0449], 'fullName': '盘锦市'}, + '忻州': {'loc': [112.4561,38.8971], 'fullName': '忻州市'}, + '吕梁': {'loc': [111.3574,37.7325], 'fullName': '吕梁市'}, + '临汾': {'loc': [111.4783,36.1615], 'fullName': '临汾市'}, + '晋中': {'loc': [112.7747,37.37], 'fullName': '晋中市'}, + '运城': {'loc': [111.1487,35.2002], 'fullName': '运城市'}, + '大同': {'loc': [113.7854,39.8035], 'fullName': '大同市'}, + '长治': {'loc': [112.8625,36.4746], 'fullName': '长治市'}, + '朔州': {'loc': [113.0713,39.6991], 'fullName': '朔州市'}, + '晋城': {'loc': [112.7856,35.6342], 'fullName': '晋城市'}, + '太原': {'loc': [112.3352,37.9413], 'fullName': '太原市'}, + '阳泉': {'loc': [113.4778,38.0951], 'fullName': '阳泉市'}, + '六安': {'loc': [116.3123,31.8329], 'fullName': '六安市'}, + '安庆': {'loc': [116.7517,30.5255], 'fullName': '安庆市'}, + '滁州': {'loc': [118.1909,32.536], 'fullName': '滁州市'}, + '宣城': {'loc': [118.8062,30.6244], 'fullName': '宣城市'}, + '阜阳': {'loc': [115.7629,32.9919], 'fullName': '阜阳市'}, + '宿州': {'loc': [117.5208,33.6841], 'fullName': '宿州市'}, + '黄山': {'loc': [118.0481,29.9542], 'fullName': '黄山市'}, + '巢湖': {'loc': [117.7734,31.4978], 'fullName': '巢湖市'}, + '亳州': {'loc': [116.1914,33.4698], 'fullName': '亳州市'}, + '池州': {'loc': [117.3889,30.2014], 'fullName': '池州市'}, + '合肥': {'loc': [117.29,32.0581], 'fullName': '合肥市'}, + '蚌埠': {'loc': [117.4109,33.1073], 'fullName': '蚌埠市'}, + '芜湖': {'loc': [118.3557,31.0858], 'fullName': '芜湖市'}, + '淮北': {'loc': [116.6968,33.6896], 'fullName': '淮北市'}, + '淮南': {'loc': [116.7847,32.7722], 'fullName': '淮南市'}, + '马鞍': {'loc': [118.6304,31.5363], 'fullName': '马鞍山市'}, + '铜陵': {'loc': [117.9382,30.9375], 'fullName': '铜陵市'}, + '南平': {'loc': [118.136,27.2845], 'fullName': '南平市'}, + '三明': {'loc': [117.5317,26.3013], 'fullName': '三明市'}, + '龙岩': {'loc': [116.8066,25.2026], 'fullName': '龙岩市'}, + '宁德': {'loc': [119.6521,26.9824], 'fullName': '宁德市'}, + '福州': {'loc': [119.4543,25.9222], 'fullName': '福州市'}, + '漳州': {'loc': [117.5757,24.3732], 'fullName': '漳州市'}, + '泉州': {'loc': [118.3228,25.1147], 'fullName': '泉州市'}, + '莆田': {'loc': [119.0918,25.3455], 'fullName': '莆田市'}, + '厦门': {'loc': [118.1689,24.6478], 'fullName': '厦门市'}, + '丽水': {'loc': [119.5642,28.1854], 'fullName': '丽水市'}, + '杭州': {'loc': [119.5313,29.8773], 'fullName': '杭州市'}, + '温州': {'loc': [120.498,27.8119], 'fullName': '温州市'}, + '宁波': {'loc': [121.5967,29.6466], 'fullName': '宁波市'}, + '舟山': {'loc': [122.2559,30.2234], 'fullName': '舟山市'}, + '台州': {'loc': [121.1353,28.6688], 'fullName': '台州市'}, + '金华': {'loc': [120.0037,29.1028], 'fullName': '金华市'}, + '衢州': {'loc': [118.6853,28.8666], 'fullName': '衢州市'}, + '绍兴': {'loc': [120.564,29.7565], 'fullName': '绍兴市'}, + '嘉兴': {'loc': [120.9155,30.6354], 'fullName': '嘉兴市'}, + '湖州': {'loc': [119.8608,30.7782], 'fullName': '湖州市'}, + '盐城': {'loc': [120.2234,33.5577], 'fullName': '盐城市'}, + '徐州': {'loc': [117.5208,34.3268], 'fullName': '徐州市'}, + '南通': {'loc': [121.1023,32.1625], 'fullName': '南通市'}, + '淮安': {'loc': [118.927,33.4039], 'fullName': '淮安市'}, + '苏州': {'loc': [120.6519,31.3989], 'fullName': '苏州市'}, + '宿迁': {'loc': [118.5535,33.7775], 'fullName': '宿迁市'}, + '连云': {'loc': [119.1248,34.552], 'fullName': '连云港市'}, + '扬州': {'loc': [119.4653,32.8162], 'fullName': '扬州市'}, + '南京': {'loc': [118.8062,31.9208], 'fullName': '南京市'}, + '泰州': {'loc': [120.0586,32.5525], 'fullName': '泰州市'}, + '无锡': {'loc': [120.3442,31.5527], 'fullName': '无锡市'}, + '常州': {'loc': [119.4543,31.5582], 'fullName': '常州市'}, + '镇江': {'loc': [119.4763,31.9702], 'fullName': '镇江市'}, + '吴忠': {'loc': [106.853,37.3755], 'fullName': '吴忠市'}, + '中卫': {'loc': [105.4028,36.9525], 'fullName': '中卫市'}, + '固原': {'loc': [106.1389,35.9363], 'fullName': '固原市'}, + '银川': {'loc': [106.3586,38.1775], 'fullName': '银川市'}, + '石嘴': {'loc': [106.4795,39.0015], 'fullName': '石嘴山市'}, + '儋州': {'loc': [109.3291,19.5653], 'fullName': '儋州市'}, + '文昌': {'loc': [110.8905,19.7823], 'fullName': '文昌市'}, + '乐东': {'loc': [109.0283,18.6301], 'fullName': '乐东黎族自治县'}, + '三亚': {'loc': [109.3716,18.3698], 'fullName': '三亚市'}, + '琼中': {'loc': [109.8413,19.0736], 'fullName': '琼中黎族苗族自治县'}, + '东方': {'loc': [108.8498,19.0414], 'fullName': '东方市'}, + '海口': {'loc': [110.3893,19.8516], 'fullName': '海口市'}, + '万宁': {'loc': [110.3137,18.8388], 'fullName': '万宁市'}, + '澄迈': {'loc': [109.9937,19.7314], 'fullName': '澄迈县'}, + '白沙': {'loc': [109.3703,19.211], 'fullName': '白沙黎族自治县'}, + '琼海': {'loc': [110.4208,19.224], 'fullName': '琼海市'}, + '昌江': {'loc': [109.0407,19.2137], 'fullName': '昌江黎族自治县'}, + '临高': {'loc': [109.6957,19.8063], 'fullName': '临高县'}, + '陵水': {'loc': [109.9924,18.5415], 'fullName': '陵水黎族自治县'}, + '屯昌': {'loc': [110.0377,19.362], 'fullName': '屯昌县'}, + '定安': {'loc': [110.3384,19.4698], 'fullName': '定安县'}, + '保亭': {'loc': [109.6284,18.6108], 'fullName': '保亭黎族苗族自治县'}, + '五指': {'loc': [109.5282,18.8299], 'fullName': '五指山市'} + }; + + // Canvas + this.defaults = {}; + this.defaults.geoDataPath = ""; + this.defaults.width = 600; + this.defaults.height = 500; + this.defaults.mapId = "0"; + this.defaults.showWords = true; // show words or not + this.defaults.levelChangeable = true; // show words or not + this.defaults.colorModel = "discrete"; // discrete or gradient color + //this.defaults.colors = ["#1f77b4", "#ff7f0e"]; + this.defaults.colors = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"]; + this.defaults.defaultAreaColor = "#dddddd"; // if area with no data show this color + this.defaults.wordStyle = {}; + this.defaults.borderStyle = {}; + + /* + this.defaults.fontSizeRatio = 1.0; + */ + + //event + this.defaults.customEvent = { + areaHoverIn : noop, + areaHoverOut : noop, + areaClick: noop, + wordHoverIn: noop, + wordHoverOut: noop, + wordClick: noop + //mousemove : function () {} + }; + this.renderCallback = noop; + + this.setOptions(options); + this.createCanvas(); + } + }); + + /** + * get value from indexes + */ + Chinamap.prototype._searchIndex = function (key, name, regionType) { + var conf = this.defaults; + var map = this; + var result; + var search = function (regionType, name) { + var shortName = name.substr(0, 2); + if (regionType === 'city') { + //prevent duplicate,张家口市,张家界市,阿拉善盟, 阿拉尔市 + if (shortName === '阿拉' || shortName === '张家') { + shortName = name.substr(0, 3); + } + } + + var hash = regionType === 'city' ? map.geoData.cityIndex : map.geoData.provinceIndex; + var result = hash[shortName]; + if (typeof result === 'undefined') { + return undefined; + } + return result[key]; + }; + + if (typeof regionType === 'undefined') { + //province, then city + if (name === '吉林市' || name === '海南藏族自治州') { + //吉林省, 吉林市; 海南省,海南藏族自治州 + result = search("city", name); + } else { + result = search("province", name) || search("city", name); + } + } else { + if (regionType === 'province') { + //province + result = search("province", name); + } else if (regionType === 'city') { + //city + result = search("city", name); + } + } + + return result; + }; + + /** + * get longitude and latitude center by city or porvince name + * regionType is optional, if it's undefined, then first search province, then city + */ + Chinamap.prototype.getLoc = function (name, regionType) { + return this._searchIndex('loc', name, regionType); + }; + + /** + * get longitude and latitude center by porvince name + */ + Chinamap.prototype.getProvinceCenter = function (name) { + return this.getLoc(name, 'province'); + }; + + /** + * get longitude and latitude center by city name + */ + Chinamap.prototype.getCityCenter = function (name) { + return this.getLoc(name, 'city'); + }; + + /** + * get format name by city or porvince name + * regionType is optional, if it's undefined, then first search province, then city + */ + Chinamap.prototype.getFormatName = function (name, regionType) { + return this._searchIndex('fullName', name, regionType); + }; + + /** + * get fullName by porvince name + */ + Chinamap.prototype.getProvinceFormatName = function (name) { + return this.getFormatName(name, 'province'); + }; + + /** + * get fullName by city name + */ + Chinamap.prototype.getCityFormatName = function (name) { + return this.getFormatName(name, 'city'); + }; + + /** + * Create dom node relate to chinamap + */ + Chinamap.prototype.createCanvas = function () { + var conf = this.defaults, + canvasStyle, + container = this.node; + + this.canvas = document.createElement("div"); + canvasStyle = this.canvas.style; + canvasStyle.position = "relative"; + canvasStyle.width = conf.width + "px"; + canvasStyle.height = conf.height + "px"; + container.appendChild(this.canvas); + + this.paper = new Raphael(this.canvas, conf.width, conf.height); + //$(this.node).css("opacity", 0.01); + + //this.node.style.position = "relative"; + this.floatTag = DataV.FloatTag()(this.canvas); + this.floatTag.css({"visibility": "hidden"}); + /* + */ + + }; + + + /** + * 获取颜色 + * Examples: + * ``` + * // 获取第二种颜色的渐变色。 + * {mode: "gradient", index: 1} + * // 获取最深的离散色。 + * {mode: "random", ratio: 0} + * // 获取最浅的离散色。 + * {mode: "random", ratio: 1} + * // 获取适中的离散色。 + * {mode: "random", ratio: 0.5} + * ``` + * @param {Object} colorJson Way to get color from color theme matrix + * @return {Array} 返回颜色数组 + */ + Chinamap.prototype.getColor = function (d) { + var colors = this.defaults.colors; + var value; + if (typeof this.colorGenerator.range === 'undefined') { + if (this.defaults.colorModel === 'discrete') { + this.colorGenerator = d3.scale.ordinal().range(colors); + } else { + this.colorGenerator = d3.scale.linear() + .range(colors.length === 1 ? colors.concat(colors) : colors) + .domain(d3.range(0, 1, 1 / (colors.length - 1)).concat([1])); + } + this.colorGenerator.min = d3.min(d3.values(this.sourceData)); + this.colorGenerator.domainBand = d3.max(d3.values(this.sourceData)) - this.colorGenerator.min; + } + //return this.colorGenerator(d.id); + //return this.colorGenerator(this.sourceData(d.properties.name)); + /* + console.log((parseInt(d.id, 10) % 100) / 50); + console.log(this.colorGenerator((parseInt(d.id, 10) % 100) / 50)); + return this.colorGenerator((parseInt(d.id, 10) % 100) / 50); + */ + value = this.sourceData[d.properties.name]; + if (typeof value === 'undefined') { + //no data area color + return this.defaults.defaultAreaColor; + } else { + if (this.defaults.colorModel === 'discrete') { + return this.colorGenerator(value); + } else { + return this.colorGenerator(this.colorGenerator.domainBand === 0 + ? 1 + : (value - this.colorGenerator.min) / this.colorGenerator.domainBand + ); + } + } + }; + + + /* + * 设置数据源 + * Examples: + * treemap数据输入的格式可以是二维数组。例如下面的数组表示2000年4个季度的天数。 + * 第1季度下面还列出了1-3月的天数。数组的第一行为四个固定的字符串"ID","name","size"和"parentID"。 + * 四列数据分别表示层次数据集中各结点的ID,名称,大小和父节点ID。叶子节点必须有大小,根结点不能有父节点ID。各结点的ID、名称必须要有。 + * ``` + * [ + * ["ID", "name", "size", "parentID"], + * [0, "2000", , ], + * [1, "season1", , 0], + * [2, "January", 31, 1], + * [3, "February", 29, 1], + * [4, "Match", 31, 1], + * [5, "season2", 91, 0], + * [6, "season3", 92, 0], + * [7, "season4", 92, 0] + * ] + * ``` + * 数据还可以是json格式。每个结点都有`name`,如果是父节点则还有`children`,如果为叶节点则还有`size`。以上数组数据对应的json数据如下: + * @param {Array|Object} source json or 2-d array + */ + Chinamap.prototype.setSource = function (source) { + var key, value, formatName; + for (key in source) { + var formatName = this.getFormatName(key); + if (typeof formatName !== 'undefined') { + this.sourceData[formatName] = source[key]; + } + } + /* + if (source instanceof Array) { + this.rawData = this._arrayToJson(source); + } else { + this.rawData = source; + } + this.source = this._remapSource(this.rawData); + this.selectedTreeNodes = [this.source[0]]; + */ + }; + + /*! + * d3 chinamap layout + */ + Chinamap.prototype.layout = function () { + /* + var chinamap = this._createTreemap() + .sort(function (a, b) { return a.value - b.value; }); + this.nodes = chinamap.nodes(this.treeNodeJson); + */ + this.projection = d3.geo.albers() + .origin([105, 30.5]) + .scale(4000); + + this.getAreaPath = d3.geo.path() + .projection(this.projection); + }; + + /*! + * 生成绘制路径 + */ + Chinamap.prototype.generatePaths = function () { + var conf = this.defaults; + var customEvent = conf.customEvent; + var map = this; + var states = map.states; + var words = map.words; + var projection = map.projection; + var getAreaPath = map.getAreaPath; + var mapCache = map.mapCache; + var paper = this.paper; + var areaBoxes = this.geoData.areaBoxes; + + var render = function (areaId, json) { + var getTitle = function (d) { + return d.properties.name; + }; + + var getCallback = function (d) { + return function () { + parseInt(areaId, 10) === 0 + ? (function () { + if (parseInt(d.properties.childNum, 10) === 1) { + return; + } + if (typeof mapCache[d.id] === 'undefined') { + d3.json(conf.geoDataPath + d.id + ".json", function (j) { + render(d.id, j); + }); + } else { + render(d.id); + } + }()) + : (function () { + if (typeof map.mapCache[0] === 'undefined') { + d3.json(conf.geoDataPath + "0.json", function (j) { + render(0, j); + }); + } else { + render(0); + } + }()); + }; + }; + var getCenterX = function (d) { + return projection(d.properties.cp)[0]; + }; + var getCenterY = function (d) { + return projection(d.properties.cp)[1]; + }; + var getText = function (d) { + return d.properties.name; + }; + + //paper.clear(); + states.forEach(function (d, i) { + d.hide(); + }); + words.forEach(function (d, i) { + d.hide(); + }); + + states = map.states = []; + words = map.words = []; + + if (typeof mapCache[areaId] === 'undefined') {//no cache + cache = mapCache[areaId] = { + states: [], + words: [] + }; + + //state + json.features.forEach(function (d, i) { + var state = paper.path(getAreaPath(d)); + d.fillColor = map.getColor(d); + state.attr({ + "fill": d.fillColor, + "stroke": "#fff" + }) + .data("info", d) + .click(customEvent.areaClick) + .mouseover(customEvent.areaHoverIn) + .mouseout(customEvent.areaHoverOut); + if (conf.levelChangeable) { + state.click(getCallback(d)); + } + states.push(state); + state.node.debugName = d.id; + cache.states.push(state); + }); + + //word + json.features.forEach(function (d, i) { + var word = paper.text(getCenterX(d), getCenterY(d), getText(d)); + word.attr({ + "font-family": '"微软雅黑", "宋体"' + }) + .data("info", d) + .click(customEvent.wordClick) + .mouseover(customEvent.wordHoverIn) + .mouseout(customEvent.wordHoverOut); + if (!conf.showWords) { + word.hide(); + } + if (conf.levelChangeable) { + word.click(getCallback(d)); + } + words.push(word); + cache.words.push(word); + }); + + states.forEach(function (d, i) { + d.data("word", words[i]) + .data("map", map); + words[i].data("state", d) + .data("map", map); + }); + + } else {//cached + //state + states = mapCache[areaId].states; + states.forEach(function (d) { + d.show(); + }); + + //word + words = mapCache[areaId].words; + if (conf.showWords) { + words.forEach(function (d) { + d.show(); + }); + } + } + + var getStatesBox = function () { + var box = {}; + var areabox = areaBoxes[areaId]; + box.x = areabox[0]; + box.y = areabox[1]; + box.width = areabox[2]; + box.height = areabox[3]; + box.x2 = areabox[2] + areabox[0]; + box.y2 = areabox[3] + areabox[1]; + return box; + }; + + (function trans () { + var recentViewBox = map.viewBox.slice(); + var statesBox = getStatesBox(); + var newBox = [statesBox.x, statesBox.y, statesBox.width, statesBox.height]; + var scale = Math.max(statesBox.width / conf.width, statesBox.height / conf.height); // the ratio that keep the font be the same size no matter what view box size is. + + var viewBoxAnim = function (oldBox, newBox, time) { + var ti = 30; + var flag = true; + var start = +new Date(); + var getBox = function (ratio) { + var box = []; + var i, l; + if (ratio >= 1) { + return newBox; + } + for (i = 0, l = oldBox.length; i < l; i++) { + box[i] = (newBox[i] - oldBox[i]) * ratio + oldBox[i]; + }; + return box; + }; + var getRatio = function () { + var t = +new Date(); + var ratio = (t - start) / time; + if (ratio > 1) { + ratio = 1; + } + var easing = function (n) { + return Math.pow(n, 1.7); + }; + return easing(ratio); + }; + var anim = function () { + if (flag === false) { + return; + } + //not permit new flame + flag = false; + //set new flame; + var ratio = getRatio(); + var box = getBox(ratio); + //draw + paper.setViewBox(box[0], box[1], box[2], box[3], true); + //console.log(box[0] + " " + box[1] + " " + box[2] + " " + box[3]); + + //clearInterval; permit new flame; + if (ratio >= 1) { + clearInterval(interval); + } + flag = true; + }; + var interval = setInterval(anim, ti); + }; + + states.forEach(function (d, i) { + states[i].attr({ + "stroke-width": 1 //2 * scale + }) + .attr(conf.wordStyle); + words[i].attr({ + "font-size": Math.round(Math.max(14 * scale, 1))//14 * scale + "px" + }) + .attr(conf.borderStyle); + }); + + if (recentViewBox.length === 0) { // first render + paper.setViewBox(newBox[0], newBox[1], newBox[2], newBox[3], true); + $("#chart").css("opacity", 1); + } else { + if (Raphael.vml) { + paper.setViewBox(newBox[0], newBox[1], newBox[2], newBox[3], true); + } else { + viewBoxAnim(recentViewBox, newBox, 750); + } + } + + map.viewBox = newBox; + map.viewBoxShift = (function (x, y, w, h, fit) { + var width = conf.width, + height = conf.height, + size = 1 / Math.max(w / width, h / height), + H, W; + if (fit) { + H = height / h; + W = width / w; + if (w * H < width) { + x -= (width - w * H) / 2 / H; + } + if (h * W < height) { + y -= (height - h * W) / 2 / W; + } + } + return { + dx: -x, + dy: -y, + scale: size + }; + }(newBox[0], newBox[1], newBox[2], newBox[3], true)); + }()); + + map.renderCallback(); + }; + + d3.json(conf.geoDataPath + conf.mapId + ".json", function(json) { + render(conf.mapId, json); + }); + + }; + + /** + * 清除画布 + */ + Chinamap.prototype.clearCanvas = function () { + //this.canvas.innerHTML = ""; + this.paper.clear(); + }; + + /** + * 计算布局位置,并渲染图表 + */ + Chinamap.prototype.render = function (options) { + this.setOptions(options); + this.clearCanvas(); + this.layout(); + this.generatePaths(); + }; + + /** + * 将点在矢量图中的位置 转化为 实际显示点相对于图片左上角的位置(像素距离) + */ + Chinamap.prototype._scaleLocToPixelLoc = function (scaleLoc) { + var map = this; + var scale = map.viewBoxShift.scale; + var viewCenter = { + 'x': map.viewBox[0] + map.viewBox[2] / 2, + 'y': map.viewBox[1] + map.viewBox[3] / 2 + }; + return { + 'x': (scaleLoc.x - viewCenter.x) * scale + map.defaults.width / 2, + 'y': (scaleLoc.y - viewCenter.y) * scale + map.defaults.height / 2 + }; + }; + + /** + * 将实际显示点相对于图片左上角的位置(像素距离) 转化为 点在矢量图中的位置 + */ + Chinamap.prototype._pixelLocToScaleLoc = function (pixelLoc) { + var map = this; + var scale = map.viewBoxShift.scale; + var viewCenter = { + 'x': map.viewBox[0] + map.viewBox[2] / 2, + 'y': map.viewBox[1] + map.viewBox[3] / 2 + }; + return { + 'x': (pixelLoc.x - map.defaults.width / 2) / scale + viewCenter.x, + 'y': (pixelLoc.y - map.defaults.height / 2) / scale + viewCenter.y + }; + }; + + /** + * 渲染城市点 + */ + Chinamap.prototype.createCityPoints = function (cities, callback) { + var conf = this.defaults; + var map = this; + var point; + var points = []; + var cb = callback || function (city) { + return this.paper.circle(city.coord[0], city.coord[1], 20) + .attr({ + "fill": "steelblue", + "fill-opacity": 0.5 + }); + }; + + cities.forEach(function (d) { + var viewCenter = { + 'x': map.viewBox[0] + map.viewBox[2] / 2, + 'y': map.viewBox[1] + map.viewBox[3] / 2 + }; + //get format name + var formatName = map.getCityFormatName(d.name); + if (typeof formatName === 'undefined') { + if (typeof d.lanlon === 'undefined') { + return; + } else { + d.formatName = d.name; + } + } else { + d.formatName = formatName; + //get loc (lan, lon), use user provided lanlon by default; + d.lanlon = d.lanlon || map.getCityCenter(d.formatName); + } + //process loc (geo projection) + d.coord = map.projection(d.lanlon); + //x and y of circle center in the container; + d.pointLoc = map._scaleLocToPixelLoc({ + 'x': d.coord[0], + 'y': d.coord[1] + }); + //callback + point = cb.call(map, d); + points.push(point); + }); + return points; + }; + + /** + * 设置自定义事件 + */ + Chinamap.prototype.setCustomEvent = function (eventName, callback) { + if ($.inArray(eventName, ["areaHoverIn", "areaHoverOut", "areaClick", + "wordHoverIn", "wordHoverOut", "wordClick"]) !== -1) { + this.defaults.customEvent[eventName] = callback; + } + }; + + /*! + * 导出Chinamap + */ + return Chinamap; +}); diff --git a/lib/charts/chord.js b/lib/charts/chord.js index d2c9624..354e08c 100644 --- a/lib/charts/chord.js +++ b/lib/charts/chord.js @@ -1,481 +1,481 @@ -/*global Raphael, d3, $, define */ -/*! - * Chord的兼容性定义 - */ -;(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]; - }); - } -})('Chord', function (require) { - var DataV = require('DataV'); - - /** - * 构造函数 - * @param {Object} node 表示在html的哪个容器中绘制该组件 - * @param {Object} options 为用户自定义的组件的属性,比如画布大小 - */ - var Chord = DataV.extend(DataV.Chart, { - initialize: function (node, options) { - this.type = "Chord"; - this.node = this.checkContainer(node); - this.matrix = []; - this.groupNames = []; //数组:记录每个group的名字 - - //图的大小设置 - this.defaults.legend = true; - this.defaults.width = 800; - this.defaults.height = 800; - - //设置用户指定的属性 - this.setOptions(options); - - this.legendArea = [20, (this.defaults.height - 20 - 220), 200, 220]; - if (this.defaults.legend) { - this.xOffset = this.legendArea[2]; - } else { - this.xOffset = 0; - } - - this.defaults.innerRadius = Math.min((this.defaults.width - this.xOffset), this.defaults.height) * 0.38; - this.defaults.outerRadius = this.defaults.innerRadius * 1.10; - //创建画布 - this.createCanvas(); - } - }); - - /** - * 创建画布 - */ - Chord.prototype.createCanvas = function () { - this.canvas = new Raphael(this.node, this.defaults.width, this.defaults.height); - canvasStyle = this.node.style; - canvasStyle.position = "relative"; - this.floatTag = DataV.FloatTag()(this.node); - this.floatTag.css({ - "visibility": "hidden" - }); - }; - - /** - * 获取颜色 - * @param {Number} i 元素类别编号 - * @return {String} 返回颜色值 - */ - Chord.prototype.getColor = function (i) { - var color = DataV.getColor(); - return color[i % color.length][0]; - }; - - /** - * 绘制弦图 - */ - Chord.prototype.render = function () { - this.layout(); - if (this.defaults.legend) { - this.legend(); - } - }; - - /** - * 绘制图例 - */ - Chord.prototype.legend = function () { - var that = this; - var paper = this.canvas; - var legendArea = this.legendArea; - var rectBn = paper.set(); - this.underBn = []; - var underBn = this.underBn; - for (i = 0; i <= this.groupNum; i++) { - //底框 - underBn.push(paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({ - "fill": "#ebebeb", - "stroke": "none" - //"r": 3 - }).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, this.groupNames[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("clicked", 0); - } - rectBn.forEach(function (d, i) { - d.mouseover(function () { - if (d.data("clicked") === 0) { - underBn[i].attr('opacity', 0.5); - underBn[i].show(); - } - }).mouseout(function () { - if (d.data("clicked") === 0) { - 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); - underBn[i].show(); - that.chordGroups.forEach(function (d) { - if (d.data('source') !== i && d.data('target') !== i) { - d.attr({ - 'fill-opacity': 0.1 - }); - } else { - d.attr({ - 'fill-opacity': 0.6 - }); - } - }); - - d.data("clicked", 1); - } else if (d.data("clicked") === 1) { - underBn[i].hide(); - d.data("clicked", 0); - that.chordGroups.forEach(function (d) { - d.attr({ - 'fill-opacity': 0.6 - }); - }); - } - }); - }); - }; - - /** - *对原始数据进行处理 - * @param {Array} table 将要被绘制成饼图的二维表数据 - */ - Chord.prototype.setSource = function (table) { - if (table[0][0] !== null) { - var t; - for (t = 0; t < table[0].length; t++) { - this.groupNames[t] = table[0][t]; - } - table = table.slice(1); // 从第一行开始,第0行舍去 - } - - var group = []; - this.groupNum = table[0].length; - var groupNum = this.groupNum; - var that = this; - table.forEach(function (d, i) { - if (d.length !== groupNum || table.length !== groupNum) { - throw new Error("The source data should be an n * n matrix!"); - } - - group[i] = []; - var s; - for (s = 0; s < groupNum; s++) { - group[i][s] = Number(d[s]); - } - }); - - var r; - for (r = 0; r < groupNum; r++) { - that.matrix[r] = group[r]; - } - }; - - /** - *创建chord布局 - */ - Chord.prototype.layout = function () { - var floatTag = this.floatTag; - var that = this; - - that.canvas.clear(); - /*var see = [ - [11975, 5871, 8916, 2868], - [1951, 10048, 2060, 6171], - [8010, 16145, 8090, 8045], - [1013, 990, 940, 6907] - ];*/ - - var chordLayout = d3.layout.chord().padding(0.05) //chord segments之间的padding间隔 - .sortSubgroups(d3.descending) //chord segments细分后的排序规则 - .matrix(that.matrix); - - /*var fillColor = d3.scale.ordinal() - .domain(d3.range(4)) - .range(["#000000", "#FFDD89", "#957244", "#F26223"]);*/ - - //groups数组:获取每个组的起始角度、数值、索引等属性 - var groups = chordLayout.groups(); - - //由内外半径、起始角度计算路径字符串 - var pathCalc = d3.svg.arc().innerRadius(that.defaults.innerRadius).outerRadius(that.defaults.outerRadius).startAngle(function (d) { - return d.startAngle; - }).endAngle(function (d) { - return d.endAngle; - }); - - var chords = chordLayout.chords(); - - //计算弦的路径曲线 - var chordCalc = d3.svg.chord().radius(that.defaults.innerRadius); - - //Raphael: Paper.path() - var donutEle; - //获取每个环形的字符串表示 - var spline; - //表示每条弦的element - var chordEle; - //每条弦的字符串表示 - var belt; - - var num; //每个group分割小格数 - var unitAngle; //每个group所占的角度 - var angle; - var radian; - var tickLine; - var tickStr; //每个tick的路径 - var xTrans, yTrans; - var aX, aY, bX, bY; //每个tick起始端点的坐标 - var anchor; - var rotateStr; - var wordStr; - var word; - var textEl; - var wXTrans, wYTrans; - var tips; - var minValue = 1000; - that.chordGroups = that.canvas.set(); - that.donutGroups = that.canvas.set(); - - $(this.node).append(this.floatTag); - - //计算某条弦被赋值为target或source的颜色 - var colorCalc = function (index) { - var i = chords[index].target.value > chords[index].source.value ? chords[index].target.index : chords[index].source.index; - return i; - }; - - //添加透明效果 - - var mouseOverDonut = function () { - floatTag.html('
' + this.data('text') + '
'); - floatTag.css({ - "visibility": "visible" - }); - that.underBn.forEach(function (d) { - d.hide(); - }); - index = this.data("donutIndex"); - that.chordGroups.forEach(function (d) { - if (d.data('source') !== index && d.data('target') !== index) { - d.attr({ - 'fill-opacity': 0.1 - }); - } else { - d.attr({ - 'fill-opacity': 0.6 - }); - } - }); - //fade(this.data("donutIndex"), 0.2); - that.underBn[index].attr('opacity', 0.5).show(); - - }; - - var mouseOutDonut = function () { - floatTag.css({ - "visibility": "hidden" - }); - index = this.data("donutIndex"); - that.chordGroups.forEach(function (d) { - if (d.data('source') !== index && d.data('target') !== index) { - d.attr({ - 'fill-opacity': 0.6 - }); - } - }); - //fade(this.data("donutIndex"), 0.6); - that.underBn[index].hide(); - }; - - var mouseoverChord = function () { - floatTag.html('
' + this.data('text') + '
'); - floatTag.css({ - "visibility": "visible" - }); - that.underBn.forEach(function (d) { - d.hide(); - }); - that.chordGroups.forEach(function (d) { - d.attr("fill-opacity", 0.1); - }); - if (navigator.appName !== "Microsoft Internet Explorer") { - this.toFront(); //把当前弦移到画布最上层 - } - this.attr("fill-opacity", 0.7); - that.underBn[this.data('source')].attr('opacity', 0.5).show(); - }; - - var mouseoutChord = function () { - floatTag.css({ - "visibility": "hidden" - }); - //alert("***"); - that.chordGroups.forEach(function (d) { - d.attr("fill-opacity", 0.6); - }); - //this.attr("fill-opacity", 0.6); - that.underBn[this.data('source')].hide(); - }; - - //画弦********************************************************* - var t; - for (t = 0; t <= chords.length - 1; t++) { - //alert(chords.length); - belt = chordCalc(chords[t]); - //hover到弦上时的效果 - tips = that.groupNames[chords[t].source.index] + " to " + that.groupNames[chords[t].target.index] + ": " + that.matrix[chords[t].source.index][chords[t].target.index] + "," + that.groupNames[chords[t].target.index] + " to " + that.groupNames[chords[t].source.index] + ": " + that.matrix[chords[t].target.index][chords[t].source.index]; - - chordEle = that.canvas.path(belt). - translate((that.defaults.width - this.xOffset) / 2 + this.xOffset, that.defaults.height / 2).attr({ - "path": belt, - "fill": that.getColor(colorCalc(t)), - "fill-opacity": 0.6, - "stroke": "#d6d6d6", - "stroke-opacity": 0.1 - }).hover(mouseoverChord, mouseoutChord).data("source", chords[t].source.index).data("target", chords[t].target.index); - //.attr("fill", fillColor(chords[t].target.index)) - chordEle.data('text', tips); - that.chordGroups.push(chordEle); - } - - - - //画圆弧********************************************************* - var i, r; - var donutName; - var nameStr; - var nameX, nameY; - var ro, a; - var sum = 0; - for (r = 0; r <= groups.length - 1; r++) { - sum += groups[r].value; - } - - for (i = 0; i <= groups.length - 1; i++) { - //画外圈的pie图************************************** - //计算每个group的path - spline = pathCalc(groups[i]); - tips = that.groupNames[i] + ": " + Math.round(groups[i].value) + " " + (groups[i].value * 100 / sum).toFixed(2) + "%"; - - donutEle = that.canvas.path(spline).translate((that.defaults.width - this.xOffset) / 2 + this.xOffset, that.defaults.height / 2).data("donutIndex", i).attr({ - "path": spline, - "fill": that.getColor(i), - "stroke": that.getColor(i) - }).mouseover(mouseOverDonut).mouseout(mouseOutDonut); - donutEle.data('text', tips); - that.donutGroups.push(donutEle); - - //每个donut上显示名称 - ro = groups[i].startAngle * 180 / Math.PI - 86 + 90; - a = (groups[i].startAngle * 180 / Math.PI - 86) * Math.PI / 180; - nameX = ((that.defaults.outerRadius - that.defaults.innerRadius) / 2 + that.defaults.innerRadius) * Math.cos(a); - nameY = ((that.defaults.outerRadius - that.defaults.innerRadius) / 2 + that.defaults.innerRadius) * Math.sin(a); - nameStr = "T" + ((that.defaults.width - that.xOffset) / 2 + that.xOffset) + "," + that.defaults.height / 2 + "R" + ro + "T" + nameX + "," + nameY; - - if ((groups[i].endAngle - groups[i].startAngle) * 180 / Math.PI > 10) { - donutName = that.canvas.text().attr("font", "12px Verdana").attr("text", that.groupNames[i]).transform(nameStr); - } - - //画刻度和刻度值************************************** - num = groups[i].value / 5000; - //最细分的每个小格代表的数值大小 - unitAngle = (groups[i].endAngle - groups[i].startAngle) * 180 / Math.PI / num; - - var j; - for (j = 0; j <= num; j++) { - //计算旋转角度和水平、竖直方向所需平移的距离 - radian = ((groups[i].startAngle * 180 / Math.PI - 90) + j * unitAngle); - angle = radian * Math.PI / 180; - xTrans = that.defaults.outerRadius * Math.cos(angle); - yTrans = that.defaults.outerRadius * Math.sin(angle); - - tickStr = "T" + ((that.defaults.width - that.xOffset) / 2 + that.xOffset) + "," + that.defaults.height / 2 + "T" + xTrans + "," + yTrans; - - //刻度线的起点终点坐标 - aX = ((that.defaults.width - that.xOffset) / 2 + that.xOffset) + xTrans; - aY = that.defaults.height / 2 + yTrans; - bX = ((that.defaults.width - that.xOffset) / 2 + that.xOffset) + (that.defaults.outerRadius + 6) * Math.cos(angle); - bY = that.defaults.height / 2 + (that.defaults.outerRadius + 6) * Math.sin(angle); - - tickLine = "M" + aX + "," + aY + "L" + bX + "," + bY; - that.canvas.path(tickLine).attr({ - 'stroke': "#929292", - "stroke-width": '1px' - }); //绘制刻度 - - //每隔五个刻度,绘制一次文字 - if (j % 2 === 0) { - //计算text-anchor - if (radian + 90 < 180) { - anchor = "start"; - } else { - anchor = "end"; - } - - //计算文字方向是否需要旋转180度 - if (radian + 90 < 180) { - rotateStr = null; - } else { - rotateStr = "R180"; - } - - wXTrans = (that.defaults.outerRadius + 10) * Math.cos(angle); - wYTrans = (that.defaults.outerRadius + 10) * Math.sin(angle); - - word = j % 2 ? "" : Math.round(((groups[i].value / num) * j) / 1000); - - wordStr = "T" + ((that.defaults.width - that.xOffset) / 2 + that.xOffset) + "," + that.defaults.height / 2 + "R" + radian - /*(groups[i].startAngle * 180 / Math.PI - 90)*/ + rotateStr + "T" + wXTrans + "," + wYTrans; - - //绘制文字 - textEl = that.canvas.text(0, 0, word).attr("font", "12px Verdana").transform(wordStr).attr("text-anchor", anchor).attr('fill', "#929292"); - } - } - } - - /*this.canvas.text().attr("font", "12px arial").translate((that.defaults.width - this.xOffset) / 2 + this.xOffset, this.defaults.height).attr("text", "The unit of the scale on the periphery is 1000. \n 刻度值的单位为1000。"); - */ - }; - - return Chord; +/*global Raphael, d3, $, define */ +/*! + * Chord的兼容性定义 + */ +;(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]; + }); + } +})('Chord', function (require) { + var DataV = require('DataV'); + + /** + * 构造函数 + * @param {Object} node 表示在html的哪个容器中绘制该组件 + * @param {Object} options 为用户自定义的组件的属性,比如画布大小 + */ + var Chord = DataV.extend(DataV.Chart, { + initialize: function (node, options) { + this.type = "Chord"; + this.node = this.checkContainer(node); + this.matrix = []; + this.groupNames = []; //数组:记录每个group的名字 + + //图的大小设置 + this.defaults.legend = true; + this.defaults.width = 800; + this.defaults.height = 800; + + //设置用户指定的属性 + this.setOptions(options); + + this.legendArea = [20, (this.defaults.height - 20 - 220), 200, 220]; + if (this.defaults.legend) { + this.xOffset = this.legendArea[2]; + } else { + this.xOffset = 0; + } + + this.defaults.innerRadius = Math.min((this.defaults.width - this.xOffset), this.defaults.height) * 0.38; + this.defaults.outerRadius = this.defaults.innerRadius * 1.10; + //创建画布 + this.createCanvas(); + } + }); + + /** + * 创建画布 + */ + Chord.prototype.createCanvas = function () { + this.canvas = new Raphael(this.node, this.defaults.width, this.defaults.height); + canvasStyle = this.node.style; + canvasStyle.position = "relative"; + this.floatTag = DataV.FloatTag()(this.node); + this.floatTag.css({ + "visibility": "hidden" + }); + }; + + /** + * 获取颜色 + * @param {Number} i 元素类别编号 + * @return {String} 返回颜色值 + */ + Chord.prototype.getColor = function (i) { + var color = DataV.getColor(); + return color[i % color.length][0]; + }; + + /** + * 绘制弦图 + */ + Chord.prototype.render = function () { + this.layout(); + if (this.defaults.legend) { + this.legend(); + } + }; + + /** + * 绘制图例 + */ + Chord.prototype.legend = function () { + var that = this; + var paper = this.canvas; + var legendArea = this.legendArea; + var rectBn = paper.set(); + this.underBn = []; + var underBn = this.underBn; + for (i = 0; i <= this.groupNum; i++) { + //底框 + underBn.push(paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({ + "fill": "#ebebeb", + "stroke": "none" + //"r": 3 + }).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, this.groupNames[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("clicked", 0); + } + rectBn.forEach(function (d, i) { + d.mouseover(function () { + if (d.data("clicked") === 0) { + underBn[i].attr('opacity', 0.5); + underBn[i].show(); + } + }).mouseout(function () { + if (d.data("clicked") === 0) { + 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); + underBn[i].show(); + that.chordGroups.forEach(function (d) { + if (d.data('source') !== i && d.data('target') !== i) { + d.attr({ + 'fill-opacity': 0.1 + }); + } else { + d.attr({ + 'fill-opacity': 0.6 + }); + } + }); + + d.data("clicked", 1); + } else if (d.data("clicked") === 1) { + underBn[i].hide(); + d.data("clicked", 0); + that.chordGroups.forEach(function (d) { + d.attr({ + 'fill-opacity': 0.6 + }); + }); + } + }); + }); + }; + + /** + *对原始数据进行处理 + * @param {Array} table 将要被绘制成饼图的二维表数据 + */ + Chord.prototype.setSource = function (table) { + if (table[0][0] !== null) { + var t; + for (t = 0; t < table[0].length; t++) { + this.groupNames[t] = table[0][t]; + } + table = table.slice(1); // 从第一行开始,第0行舍去 + } + + var group = []; + this.groupNum = table[0].length; + var groupNum = this.groupNum; + var that = this; + table.forEach(function (d, i) { + if (d.length !== groupNum || table.length !== groupNum) { + throw new Error("The source data should be an n * n matrix!"); + } + + group[i] = []; + var s; + for (s = 0; s < groupNum; s++) { + group[i][s] = Number(d[s]); + } + }); + + var r; + for (r = 0; r < groupNum; r++) { + that.matrix[r] = group[r]; + } + }; + + /** + *创建chord布局 + */ + Chord.prototype.layout = function () { + var floatTag = this.floatTag; + var that = this; + + that.canvas.clear(); + /*var see = [ + [11975, 5871, 8916, 2868], + [1951, 10048, 2060, 6171], + [8010, 16145, 8090, 8045], + [1013, 990, 940, 6907] + ];*/ + + var chordLayout = d3.layout.chord().padding(0.05) //chord segments之间的padding间隔 + .sortSubgroups(d3.descending) //chord segments细分后的排序规则 + .matrix(that.matrix); + + /*var fillColor = d3.scale.ordinal() + .domain(d3.range(4)) + .range(["#000000", "#FFDD89", "#957244", "#F26223"]);*/ + + //groups数组:获取每个组的起始角度、数值、索引等属性 + var groups = chordLayout.groups(); + + //由内外半径、起始角度计算路径字符串 + var pathCalc = d3.svg.arc().innerRadius(that.defaults.innerRadius).outerRadius(that.defaults.outerRadius).startAngle(function (d) { + return d.startAngle; + }).endAngle(function (d) { + return d.endAngle; + }); + + var chords = chordLayout.chords(); + + //计算弦的路径曲线 + var chordCalc = d3.svg.chord().radius(that.defaults.innerRadius); + + //Raphael: Paper.path() + var donutEle; + //获取每个环形的字符串表示 + var spline; + //表示每条弦的element + var chordEle; + //每条弦的字符串表示 + var belt; + + var num; //每个group分割小格数 + var unitAngle; //每个group所占的角度 + var angle; + var radian; + var tickLine; + var tickStr; //每个tick的路径 + var xTrans, yTrans; + var aX, aY, bX, bY; //每个tick起始端点的坐标 + var anchor; + var rotateStr; + var wordStr; + var word; + var textEl; + var wXTrans, wYTrans; + var tips; + var minValue = 1000; + that.chordGroups = that.canvas.set(); + that.donutGroups = that.canvas.set(); + + $(this.node).append(this.floatTag); + + //计算某条弦被赋值为target或source的颜色 + var colorCalc = function (index) { + var i = chords[index].target.value > chords[index].source.value ? chords[index].target.index : chords[index].source.index; + return i; + }; + + //添加透明效果 + + var mouseOverDonut = function () { + floatTag.html('
' + this.data('text') + '
'); + floatTag.css({ + "visibility": "visible" + }); + that.underBn.forEach(function (d) { + d.hide(); + }); + index = this.data("donutIndex"); + that.chordGroups.forEach(function (d) { + if (d.data('source') !== index && d.data('target') !== index) { + d.attr({ + 'fill-opacity': 0.1 + }); + } else { + d.attr({ + 'fill-opacity': 0.6 + }); + } + }); + //fade(this.data("donutIndex"), 0.2); + that.underBn[index].attr('opacity', 0.5).show(); + + }; + + var mouseOutDonut = function () { + floatTag.css({ + "visibility": "hidden" + }); + index = this.data("donutIndex"); + that.chordGroups.forEach(function (d) { + if (d.data('source') !== index && d.data('target') !== index) { + d.attr({ + 'fill-opacity': 0.6 + }); + } + }); + //fade(this.data("donutIndex"), 0.6); + that.underBn[index].hide(); + }; + + var mouseoverChord = function () { + floatTag.html('
' + this.data('text') + '
'); + floatTag.css({ + "visibility": "visible" + }); + that.underBn.forEach(function (d) { + d.hide(); + }); + that.chordGroups.forEach(function (d) { + d.attr("fill-opacity", 0.1); + }); + if (navigator.appName !== "Microsoft Internet Explorer") { + this.toFront(); //把当前弦移到画布最上层 + } + this.attr("fill-opacity", 0.7); + that.underBn[this.data('source')].attr('opacity', 0.5).show(); + }; + + var mouseoutChord = function () { + floatTag.css({ + "visibility": "hidden" + }); + //alert("***"); + that.chordGroups.forEach(function (d) { + d.attr("fill-opacity", 0.6); + }); + //this.attr("fill-opacity", 0.6); + that.underBn[this.data('source')].hide(); + }; + + //画弦********************************************************* + var t; + for (t = 0; t <= chords.length - 1; t++) { + //alert(chords.length); + belt = chordCalc(chords[t]); + //hover到弦上时的效果 + tips = that.groupNames[chords[t].source.index] + " to " + that.groupNames[chords[t].target.index] + ": " + that.matrix[chords[t].source.index][chords[t].target.index] + "," + that.groupNames[chords[t].target.index] + " to " + that.groupNames[chords[t].source.index] + ": " + that.matrix[chords[t].target.index][chords[t].source.index]; + + chordEle = that.canvas.path(belt). + translate((that.defaults.width - this.xOffset) / 2 + this.xOffset, that.defaults.height / 2).attr({ + "path": belt, + "fill": that.getColor(colorCalc(t)), + "fill-opacity": 0.6, + "stroke": "#d6d6d6", + "stroke-opacity": 0.1 + }).hover(mouseoverChord, mouseoutChord).data("source", chords[t].source.index).data("target", chords[t].target.index); + //.attr("fill", fillColor(chords[t].target.index)) + chordEle.data('text', tips); + that.chordGroups.push(chordEle); + } + + + + //画圆弧********************************************************* + var i, r; + var donutName; + var nameStr; + var nameX, nameY; + var ro, a; + var sum = 0; + for (r = 0; r <= groups.length - 1; r++) { + sum += groups[r].value; + } + + for (i = 0; i <= groups.length - 1; i++) { + //画外圈的pie图************************************** + //计算每个group的path + spline = pathCalc(groups[i]); + tips = that.groupNames[i] + ": " + Math.round(groups[i].value) + " " + (groups[i].value * 100 / sum).toFixed(2) + "%"; + + donutEle = that.canvas.path(spline).translate((that.defaults.width - this.xOffset) / 2 + this.xOffset, that.defaults.height / 2).data("donutIndex", i).attr({ + "path": spline, + "fill": that.getColor(i), + "stroke": that.getColor(i) + }).mouseover(mouseOverDonut).mouseout(mouseOutDonut); + donutEle.data('text', tips); + that.donutGroups.push(donutEle); + + //每个donut上显示名称 + ro = groups[i].startAngle * 180 / Math.PI - 86 + 90; + a = (groups[i].startAngle * 180 / Math.PI - 86) * Math.PI / 180; + nameX = ((that.defaults.outerRadius - that.defaults.innerRadius) / 2 + that.defaults.innerRadius) * Math.cos(a); + nameY = ((that.defaults.outerRadius - that.defaults.innerRadius) / 2 + that.defaults.innerRadius) * Math.sin(a); + nameStr = "T" + ((that.defaults.width - that.xOffset) / 2 + that.xOffset) + "," + that.defaults.height / 2 + "R" + ro + "T" + nameX + "," + nameY; + + if ((groups[i].endAngle - groups[i].startAngle) * 180 / Math.PI > 10) { + donutName = that.canvas.text().attr("font", "12px Verdana").attr("text", that.groupNames[i]).transform(nameStr); + } + + //画刻度和刻度值************************************** + num = groups[i].value / 5000; + //最细分的每个小格代表的数值大小 + unitAngle = (groups[i].endAngle - groups[i].startAngle) * 180 / Math.PI / num; + + var j; + for (j = 0; j <= num; j++) { + //计算旋转角度和水平、竖直方向所需平移的距离 + radian = ((groups[i].startAngle * 180 / Math.PI - 90) + j * unitAngle); + angle = radian * Math.PI / 180; + xTrans = that.defaults.outerRadius * Math.cos(angle); + yTrans = that.defaults.outerRadius * Math.sin(angle); + + tickStr = "T" + ((that.defaults.width - that.xOffset) / 2 + that.xOffset) + "," + that.defaults.height / 2 + "T" + xTrans + "," + yTrans; + + //刻度线的起点终点坐标 + aX = ((that.defaults.width - that.xOffset) / 2 + that.xOffset) + xTrans; + aY = that.defaults.height / 2 + yTrans; + bX = ((that.defaults.width - that.xOffset) / 2 + that.xOffset) + (that.defaults.outerRadius + 6) * Math.cos(angle); + bY = that.defaults.height / 2 + (that.defaults.outerRadius + 6) * Math.sin(angle); + + tickLine = "M" + aX + "," + aY + "L" + bX + "," + bY; + that.canvas.path(tickLine).attr({ + 'stroke': "#929292", + "stroke-width": '1px' + }); //绘制刻度 + + //每隔五个刻度,绘制一次文字 + if (j % 2 === 0) { + //计算text-anchor + if (radian + 90 < 180) { + anchor = "start"; + } else { + anchor = "end"; + } + + //计算文字方向是否需要旋转180度 + if (radian + 90 < 180) { + rotateStr = null; + } else { + rotateStr = "R180"; + } + + wXTrans = (that.defaults.outerRadius + 10) * Math.cos(angle); + wYTrans = (that.defaults.outerRadius + 10) * Math.sin(angle); + + word = j % 2 ? "" : Math.round(((groups[i].value / num) * j) / 1000); + + wordStr = "T" + ((that.defaults.width - that.xOffset) / 2 + that.xOffset) + "," + that.defaults.height / 2 + "R" + radian + /*(groups[i].startAngle * 180 / Math.PI - 90)*/ + rotateStr + "T" + wXTrans + "," + wYTrans; + + //绘制文字 + textEl = that.canvas.text(0, 0, word).attr("font", "12px Verdana").transform(wordStr).attr("text-anchor", anchor).attr('fill', "#929292"); + } + } + } + + /*this.canvas.text().attr("font", "12px arial").translate((that.defaults.width - this.xOffset) / 2 + this.xOffset, this.defaults.height).attr("text", "The unit of the scale on the periphery is 1000. \n 刻度值的单位为1000。"); + */ + }; + + return Chord; }); \ No newline at end of file diff --git a/lib/charts/column.js b/lib/charts/column.js index 6236cd0..6c0de8c 100644 --- a/lib/charts/column.js +++ b/lib/charts/column.js @@ -1,510 +1,510 @@ -/*global Raphael, d3, $, define, _ */ -/*! - * Column图的兼容性定义 - */ -;(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];}); - } -})('Column', function (require) { - var DataV = require('DataV'); - - /** - * Column构造函数 - * Creates Column in a DOM node with id "chart", default width is 522; height is 522px; - * Options: - * - * - `width` 宽度,默认为节点宽度 - * - `yBase` 纵坐标的基线值,有的以0为起始值,有的则以数据中的最小值为起始值 - * - `gap` 组与组之间的缝隙宽度 - * - * Examples: - * ``` - * var column = new Column("chart", {"width": 500, "height": 600, "typeNames": ["Y", "Z"]}); - * ``` - * @param {Mix} node The dom node or dom node Id - * @param {Object} options options json object for determin column style. - */ - var Column = DataV.extend(DataV.Chart, { - initialize: function (node, options) { - this.type = "Column"; - this.node = this.checkContainer(node); - - /** - * 柱纬度 - */ - this.dimension.column = { - type: "string", - required: true, - index: 0 - }; - /** - * 横向纬度 - */ - this.dimension.x = { - type: "string", - required: true, - index: 1 - }; - /** - * 值纬度 - */ - this.dimension.value = { - type: "number", - required: true, - index: 2 - }; - - this.defaults.typeNames = []; - // canvas parameters - this.defaults.width = 522; - this.defaults.height = 522; - this.defaults.margin = 50; - this.defaults.gap = 15; - this.defaults.circleR = 3; - this.defaults.barColor = ["#308BE6","#8EEC00","#DDDF0D"]; - this.defaults.xTickNumber = 5; - this.defaults.yTickNumber = 5; - - this.defaults.yBase = undefined; - - //图例区域的左上顶点坐标x,y,宽,高 - this.defaults.legendArea = [422, 50, 472, 220]; - //散点矩阵区域的左上顶点坐标x,y,宽,高 - this.defaults.diagramArea = [50, 50, 422, 472]; - this.columnSet = []; - - this.setOptions(options); - this.createCanvas(); - this.initEvents(); - } - }); - - /** - * 创建画布 - */ - Column.prototype.createCanvas = function () { - var conf = this.defaults; - this.node.style.position = "relative"; - this.canvas = new Raphael(this.node, conf.width, conf.height); - }; - - Column.prototype.initEvents = function () { - var that = this; - this.on('legendOver', function (columnIndex) { - that.columnSet.forEach(function (set, index) { - if (index !== columnIndex) { - set.attr({ - "fill-opacity": 0.3 - }); - } - }); - }); - - this.on('legendOut', function (columnIndex) { - that.columnSet.forEach(function (set, index) { - set.attr({ - "fill-opacity": 1 - }); - }); - }); - - this.on('legendClick', function (clicked, columnIndex) { - that.clicked = clicked; - that.clickedColumnIndex = columnIndex; - that.columnSet.forEach(function (set, index) { - if (index !== columnIndex) { - if (clicked) { - set.attr({"fill-opacity": 0.1}); - } else { - set.attr({"fill-opacity": 0.5}); - } - } else { - set.attr({"fill-opacity": 1}); - } - }); - }); - }; - - /** - * 设置数据源 - * Examples: - * ``` - * column.setSource(source); - * ``` - * @param {Array} source 数据源 第一列为排布在x轴的数据,后n列为排布在y轴的数据 - */ - Column.prototype.setSource = function (source, map) { - var conf = this.defaults; - map = this.map(map); - var dataTable; - if (DataV.detect(source) === 'Table_WITH_HEAD') { - dataTable = DataV.collectionify(source); - } else { - dataTable = source; - } - this.columns = _.groupBy(dataTable, map.column); - this.columnCount = _.keys(this.columns).length; - - conf.xAxisData = _.pluck(_.first(_.values(this.columns)), map.x); - conf.xTickNumber = Math.min(conf.xAxisData.length, conf.xTickNumber); - // 纵坐标的范围 - conf.yExtent = d3.extent(dataTable, function (item) { - return item[map.value]; - }); - // 纵坐标基线值 - if (conf.yBase !== undefined) { - conf.yExtent.push(conf.yBase); - conf.yExtent = d3.extent(conf.yExtent); - } - }; - - /** - * 设置坐标轴 - */ - Column.prototype.setAxis = function () { - var conf = this.defaults; - var tagWidth = conf.width / 5 > 50 ? 50 : conf.width / 5; - conf.legendArea = [conf.width - tagWidth - conf.margin, 0, conf.width, conf.height]; - conf.diagramArea = [0, 0, conf.width - tagWidth - conf.margin, conf.height]; - var w = conf.diagramArea[2] - 2 * conf.margin; - var h = conf.diagramArea[3] - conf.margin; - - //设置x轴 - this.x = d3.scale.linear().domain([0, conf.xAxisData.length]).range([conf.margin, w]); - //设置y轴 - this.value = d3.scale.linear().domain(conf.yExtent).range([h, conf.margin]); - var xRange = this.x.range(); - var valueRange = this.value.range(); - var axis = this.axisPosition = { - left: xRange[0], - right: xRange[1], - up: valueRange[1], - down: valueRange[0] - }; - var columnsMaxLen = _.max(this.columns, function (column) { - return column.length; - }).length; - this.barWidth = (axis.right - axis.left - columnsMaxLen * conf.gap) / columnsMaxLen / _.keys(this.columns).length; - }; - - /** - * 绘制坐标 - */ - Column.prototype.drawAxis = function () { - var that = this; - var conf = this.defaults; - var paper = this.canvas; - var i, j, k, l; - //画坐标轴 - var axisLines = paper.set(); - var tickText = paper.set(); - var axis = this.axisPosition; - var ticks; - // X轴 - ticks = this.x.ticks(conf.xTickNumber); - console.log(ticks); - var range = this.x.range(); - - // 修复显示不从第一个x轴单位显示的bug - for (j = 0; j < ticks.length; j++) { - // 修改x轴单位显示在所有Column组的中间位置 - // 修复x轴单位对于柱位置的偏移 - var x = this.x(ticks[j]) + conf.gap / 2 + this.columnCount * Math.floor(this.barWidth) / 2; - tickText.push(paper.text(x, axis.down + 14, conf.xAxisData[ticks[j]]).rotate(0, x, axis.up)); - axisLines.push(paper.path("M" + x + "," + axis.down + "L" + x + "," + (axis.down + 5))); - } - - tickText.attr({ - "fill": "#878791", - "fill-opacity": 0.7, - "font-size": 12, - "text-anchor": "middle" - }); - - axisLines.push(paper.path("M" + axis.left + "," + axis.up + "L" + axis.left + "," + axis.down)); - axisLines.attr({ - "stroke": "#D7D7D7", - "stroke-width": 2 - }); - //Y轴 - ticks = this.value.ticks(conf.yTickNumber); - for (j = 0; j < ticks.length; j++) { - tickText.push(paper.text(axis.left - 8, this.value(ticks[j]), ticks[j]).attr({ - "fill": "#878791", - "fill-opacity": 0.7, - "font-size": 12, - "text-anchor": "end" - }).rotate(0, axis.right + 6, this.value(ticks[j]))); - axisLines.push(paper.path("M" + axis.left + "," + this.value(ticks[j]) + "L" + (axis.left - 5) + "," + this.value(ticks[j]))); - } - axisLines.push(paper.path("M" + axis.left + "," + axis.down + "L" + axis.right + "," + axis.down)); - axisLines.attr({ - "stroke": "#D7D7D7", - "stroke-width": 2 - }); - - var numOfHLine = d3.round((axis.down - axis.up) / 30 - 1); - var hLines = paper.set(); - for (j = 1; j <= numOfHLine; j++) { - var hLinesPos = axis.down - j * 30; - hLines.push(paper.path("M" + axis.left + "," + hLinesPos + "L" + axis.right + "," + hLinesPos)); - } - hLines.attr({ - "stroke": "#ECECEC", - "stroke-width": 1 - }); - }; - - /** - * 进行柱状图的绘制 - */ - Column.prototype.drawDiagram = function () { - var that = this; - var conf = this.defaults; - var axis = this.axisPosition; - var paper = this.canvas; - var dim = that.dimension; - //bars - var barWidth = this.barWidth; - var columnCount = this.columnCount; - var columnSet = this.columnSet; - var values = _.values(this.columns); - var tagSet = paper.set(); - - //bars - var mouseOverBar = function (event) { - var columnIndex = this.data('column'); - var xIndex = this.data('index'); - if (that.clicked && that.clickedColumnIndex !== columnIndex) { - return; - } - tagSet.remove(); - var currentSet = columnSet.filter(function (set, columnIndex) { - return that.clicked ? that.clickedColumnIndex === columnIndex : true; - }); - currentSet.forEach(function (set, columnIndex) { - set.animate({ - "fill-opacity": 0.3 - }, 10); - set[xIndex].animate({ - "fill-opacity":1 - }, 10); - }); - - var hovered = currentSet.map(function (set) { - return set[xIndex]; - }); - var xPos = _.max(hovered, function (item) { - return item.attrs.x; - }).attrs.x + barWidth + 8; - - var y = _.map(hovered, function (item) { - return item.attrs.y; - }); - // TODO: 防遮罩算法 - for (var i = 1; i < y.length; i++) { - for (var j = i - 1; j >= 0; j--) { - var overlapped = y.filter(function (item, index) { - return index < i && Math.abs(item - y[i]) < 20; - }); - if (overlapped.length > 0) { - var extent = d3.extent(overlapped); - if (y[i] <= extent[0]) { - y[i] = extent[0] - 20; - } else { - y[i] = extent[1] + 20; - } - } - } - } - hovered.forEach(function (item, columnIndex) { - var yPos = y[columnIndex]; - var valueLabel = '' + values[columnIndex][xIndex][dim.value.index]; - var textWidth = 5 * valueLabel.length + 20; - - var rect = paper.rect(xPos, yPos - 10, textWidth, 20, 2).attr({ - "fill": conf.barColor[columnIndex], - "fill-opacity": 1, - "stroke": "none" - }); - var path = paper.path("M" + xPos + "," + (yPos - 4) + "L" + (xPos - 8) + "," + yPos + "L" + xPos + "," + (yPos + 4) + "V" + yPos + "Z").attr({ - "fill" : conf.barColor[columnIndex], - "stroke" : conf.barColor[columnIndex] - }); - var text = paper.text(xPos + 16, yPos, valueLabel).attr({ - "fill": "#ffffff", - "fill-opacity": 1, - "font-weight": "bold", - "font-size": 12, - "text-anchor": "middle" - }); - tagSet.push(rect, path, text); - }); - - xPos = hovered.reduce(function (pre, cur) { - return pre + cur.attrs.x; - }, 0) / hovered.length + barWidth / 2; - var xLabel = '' + values[columnIndex][xIndex][dim.x.index]; - var textWidth = 6 * xLabel.length + 20; - //axis x rect - var rect = paper.rect(xPos - textWidth / 2, axis.down + 8, textWidth, 20, 2).attr({ - "fill": "#5f5f5f", - "fill-opacity": 1, - "stroke": "none" - }); - // axis x text - var text = paper.text(xPos, axis.down + 18, xLabel).attr({ - "fill": "#ffffff", - "fill-opacity": 1, - "font-weight": "bold", - "font-size": 12, - "text-anchor": "middle" - }); - var arrow = paper.path("M" + (xPos - 4) + "," + (axis.down + 8) + "L" + xPos + "," + axis.down + - "L" + (xPos + 4) + "," + (axis.down + 8) + "H" + xPos + "Z").attr({ - "fill": "#5F5F5F", - "stroke": "#5F5F5F" - }); - tagSet.push(rect, text, arrow); - }; - - var mouseOutBar = function (event) { - var columnIndex = this.data('column'); - var xIndex = this.data('index'); - var currentSet = columnSet.filter(function (set, columnIndex) { - return that.clicked ? that.clickedColumnIndex === columnIndex : true; - }); - tagSet.animate({"opacity": 0}, 1000, function () { - tagSet.remove(); - }); - currentSet.forEach(function (set, columnIndex) { - set.attr({"fill-opacity": 1}); - }); - }; - - values.forEach(function (column, index) { - columnSet[index] = paper.set(); - column.forEach(function (row, i) { - var value = row[dim.value.index]; - var height = that.value(value); - var x = that.x(i); - var rect = paper.rect(x + barWidth * index + conf.gap / 2, height, barWidth, axis.down - height).attr({ - "fill": conf.barColor[index], - "fill-opacity": 1, - "stroke": "none" - }); - rect.data('column', index).data('index', i); - rect.mouseover(mouseOverBar); - rect.mouseout(mouseOutBar); - columnSet[index].push(rect); - }); - }); - }; - - /** - * 绘制图例 - */ - Column.prototype.drawLegend = function () { - var that = this; - var paper = this.canvas; - var legendSet = paper.set(); - var bgSet = paper.set(); - var conf = this.defaults; - var legendArea = conf.legendArea; - var columnCount = this.columnCount; - //legend - var mouseOverLegend = function (event) { - if (legendSet.clicked) { - return; - } - bgSet[this.data('type')].attr({ - "fill-opacity":0.5 - }); - that.fire('legendOver', this.data('type')); - }; - - var mouseOutLegend = function (event) { - if (legendSet.clicked) { - return; - } - bgSet[this.data('type')].attr({"fill-opacity": 0}); - that.fire('legendOut', this.data('type')); - }; - - var clickLegend = function (event) { - if (legendSet.clicked && legendSet.clickedColumn === this.data('type')) { - legendSet.clicked = false; - } else { - legendSet.clicked = true; - legendSet.clickedColumn = this.data('type'); - } - bgSet.attr({"fill-opacity": 0}); - bgSet[this.data('type')].attr({ - "fill-opacity": legendSet.clicked ? 1 : 0 - }); - that.fire('legendClick', legendSet.clicked, this.data('type')); - }; - - var labels = _.keys(this.columns); - for (var i = 0; i < labels.length; i++) { - //底框 - bgSet.push(paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({ - "fill": "#ebebeb", - "fill-opacity": 0, - "stroke": "none" - })); - // 色框 - paper.rect(legendArea[0] + 10 + 3, legendArea[1] + 10 + (20 + 3) * i + 6, 16, 8).attr({ - "fill": conf.barColor[i], - "stroke": "none" - }); - // 文字 - paper.text(legendArea[0] + 10 + 3 + 16 + 8, legendArea[1] + 10 + (20 + 3) * i + 10, labels[i]).attr({ - "fill": "black", - "fill-opacity": 1, - "font-family": "Verdana", - "font-size": 12, - "text-anchor": "start" - }); - // 选框 - var rect = paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({ - "fill": "white", - "fill-opacity": 0, - "stroke": "none" - }).data("type", i); - rect.mouseover(mouseOverLegend); - rect.mouseout(mouseOutLegend); - rect.click(clickLegend); - legendSet.push(rect); - } - }; - - /** - * 绘制柱状图 - * Options: - * - * - `width` 宽度,默认为节点宽度 - * - `typeNames` 指定y轴上数据类目 - * - * Examples: - * ``` - * column.render({"width": 1024}) - * ``` - * @param {Object} options options json object for determin column style. - */ - Column.prototype.render = function (options) { - this.setOptions(options); - this.canvas.clear(); - this.setAxis(); - this.drawAxis(); - this.drawDiagram(); - this.drawLegend(); - }; - /*! - * 导出 - */ - return Column; -}); +/*global Raphael, d3, $, define, _ */ +/*! + * Column图的兼容性定义 + */ +;(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];}); + } +})('Column', function (require) { + var DataV = require('DataV'); + + /** + * Column构造函数 + * Creates Column in a DOM node with id "chart", default width is 522; height is 522px; + * Options: + * + * - `width` 宽度,默认为节点宽度 + * - `yBase` 纵坐标的基线值,有的以0为起始值,有的则以数据中的最小值为起始值 + * - `gap` 组与组之间的缝隙宽度 + * + * Examples: + * ``` + * var column = new Column("chart", {"width": 500, "height": 600, "typeNames": ["Y", "Z"]}); + * ``` + * @param {Mix} node The dom node or dom node Id + * @param {Object} options options json object for determin column style. + */ + var Column = DataV.extend(DataV.Chart, { + initialize: function (node, options) { + this.type = "Column"; + this.node = this.checkContainer(node); + + /** + * 柱纬度 + */ + this.dimension.column = { + type: "string", + required: true, + index: 0 + }; + /** + * 横向纬度 + */ + this.dimension.x = { + type: "string", + required: true, + index: 1 + }; + /** + * 值纬度 + */ + this.dimension.value = { + type: "number", + required: true, + index: 2 + }; + + this.defaults.typeNames = []; + // canvas parameters + this.defaults.width = 522; + this.defaults.height = 522; + this.defaults.margin = 50; + this.defaults.gap = 15; + this.defaults.circleR = 3; + this.defaults.barColor = ["#308BE6","#8EEC00","#DDDF0D"]; + this.defaults.xTickNumber = 5; + this.defaults.yTickNumber = 5; + + this.defaults.yBase = undefined; + + //图例区域的左上顶点坐标x,y,宽,高 + this.defaults.legendArea = [422, 50, 472, 220]; + //散点矩阵区域的左上顶点坐标x,y,宽,高 + this.defaults.diagramArea = [50, 50, 422, 472]; + this.columnSet = []; + + this.setOptions(options); + this.createCanvas(); + this.initEvents(); + } + }); + + /** + * 创建画布 + */ + Column.prototype.createCanvas = function () { + var conf = this.defaults; + this.node.style.position = "relative"; + this.canvas = new Raphael(this.node, conf.width, conf.height); + }; + + Column.prototype.initEvents = function () { + var that = this; + this.on('legendOver', function (columnIndex) { + that.columnSet.forEach(function (set, index) { + if (index !== columnIndex) { + set.attr({ + "fill-opacity": 0.3 + }); + } + }); + }); + + this.on('legendOut', function (columnIndex) { + that.columnSet.forEach(function (set, index) { + set.attr({ + "fill-opacity": 1 + }); + }); + }); + + this.on('legendClick', function (clicked, columnIndex) { + that.clicked = clicked; + that.clickedColumnIndex = columnIndex; + that.columnSet.forEach(function (set, index) { + if (index !== columnIndex) { + if (clicked) { + set.attr({"fill-opacity": 0.1}); + } else { + set.attr({"fill-opacity": 0.5}); + } + } else { + set.attr({"fill-opacity": 1}); + } + }); + }); + }; + + /** + * 设置数据源 + * Examples: + * ``` + * column.setSource(source); + * ``` + * @param {Array} source 数据源 第一列为排布在x轴的数据,后n列为排布在y轴的数据 + */ + Column.prototype.setSource = function (source, map) { + var conf = this.defaults; + map = this.map(map); + var dataTable; + if (DataV.detect(source) === 'Table_WITH_HEAD') { + dataTable = DataV.collectionify(source); + } else { + dataTable = source; + } + this.columns = _.groupBy(dataTable, map.column); + this.columnCount = _.keys(this.columns).length; + + conf.xAxisData = _.pluck(_.first(_.values(this.columns)), map.x); + conf.xTickNumber = Math.min(conf.xAxisData.length, conf.xTickNumber); + // 纵坐标的范围 + conf.yExtent = d3.extent(dataTable, function (item) { + return item[map.value]; + }); + // 纵坐标基线值 + if (conf.yBase !== undefined) { + conf.yExtent.push(conf.yBase); + conf.yExtent = d3.extent(conf.yExtent); + } + }; + + /** + * 设置坐标轴 + */ + Column.prototype.setAxis = function () { + var conf = this.defaults; + var tagWidth = conf.width / 5 > 50 ? 50 : conf.width / 5; + conf.legendArea = [conf.width - tagWidth - conf.margin, 0, conf.width, conf.height]; + conf.diagramArea = [0, 0, conf.width - tagWidth - conf.margin, conf.height]; + var w = conf.diagramArea[2] - 2 * conf.margin; + var h = conf.diagramArea[3] - conf.margin; + + //设置x轴 + this.x = d3.scale.linear().domain([0, conf.xAxisData.length]).range([conf.margin, w]); + //设置y轴 + this.value = d3.scale.linear().domain(conf.yExtent).range([h, conf.margin]); + var xRange = this.x.range(); + var valueRange = this.value.range(); + var axis = this.axisPosition = { + left: xRange[0], + right: xRange[1], + up: valueRange[1], + down: valueRange[0] + }; + var columnsMaxLen = _.max(this.columns, function (column) { + return column.length; + }).length; + this.barWidth = (axis.right - axis.left - columnsMaxLen * conf.gap) / columnsMaxLen / _.keys(this.columns).length; + }; + + /** + * 绘制坐标 + */ + Column.prototype.drawAxis = function () { + var that = this; + var conf = this.defaults; + var paper = this.canvas; + var i, j, k, l; + //画坐标轴 + var axisLines = paper.set(); + var tickText = paper.set(); + var axis = this.axisPosition; + var ticks; + // X轴 + ticks = this.x.ticks(conf.xTickNumber); + console.log(ticks); + var range = this.x.range(); + + // 修复显示不从第一个x轴单位显示的bug + for (j = 0; j < ticks.length; j++) { + // 修改x轴单位显示在所有Column组的中间位置 + // 修复x轴单位对于柱位置的偏移 + var x = this.x(ticks[j]) + conf.gap / 2 + this.columnCount * Math.floor(this.barWidth) / 2; + tickText.push(paper.text(x, axis.down + 14, conf.xAxisData[ticks[j]]).rotate(0, x, axis.up)); + axisLines.push(paper.path("M" + x + "," + axis.down + "L" + x + "," + (axis.down + 5))); + } + + tickText.attr({ + "fill": "#878791", + "fill-opacity": 0.7, + "font-size": 12, + "text-anchor": "middle" + }); + + axisLines.push(paper.path("M" + axis.left + "," + axis.up + "L" + axis.left + "," + axis.down)); + axisLines.attr({ + "stroke": "#D7D7D7", + "stroke-width": 2 + }); + //Y轴 + ticks = this.value.ticks(conf.yTickNumber); + for (j = 0; j < ticks.length; j++) { + tickText.push(paper.text(axis.left - 8, this.value(ticks[j]), ticks[j]).attr({ + "fill": "#878791", + "fill-opacity": 0.7, + "font-size": 12, + "text-anchor": "end" + }).rotate(0, axis.right + 6, this.value(ticks[j]))); + axisLines.push(paper.path("M" + axis.left + "," + this.value(ticks[j]) + "L" + (axis.left - 5) + "," + this.value(ticks[j]))); + } + axisLines.push(paper.path("M" + axis.left + "," + axis.down + "L" + axis.right + "," + axis.down)); + axisLines.attr({ + "stroke": "#D7D7D7", + "stroke-width": 2 + }); + + var numOfHLine = d3.round((axis.down - axis.up) / 30 - 1); + var hLines = paper.set(); + for (j = 1; j <= numOfHLine; j++) { + var hLinesPos = axis.down - j * 30; + hLines.push(paper.path("M" + axis.left + "," + hLinesPos + "L" + axis.right + "," + hLinesPos)); + } + hLines.attr({ + "stroke": "#ECECEC", + "stroke-width": 1 + }); + }; + + /** + * 进行柱状图的绘制 + */ + Column.prototype.drawDiagram = function () { + var that = this; + var conf = this.defaults; + var axis = this.axisPosition; + var paper = this.canvas; + var dim = that.dimension; + //bars + var barWidth = this.barWidth; + var columnCount = this.columnCount; + var columnSet = this.columnSet; + var values = _.values(this.columns); + var tagSet = paper.set(); + + //bars + var mouseOverBar = function (event) { + var columnIndex = this.data('column'); + var xIndex = this.data('index'); + if (that.clicked && that.clickedColumnIndex !== columnIndex) { + return; + } + tagSet.remove(); + var currentSet = columnSet.filter(function (set, columnIndex) { + return that.clicked ? that.clickedColumnIndex === columnIndex : true; + }); + currentSet.forEach(function (set, columnIndex) { + set.animate({ + "fill-opacity": 0.3 + }, 10); + set[xIndex].animate({ + "fill-opacity":1 + }, 10); + }); + + var hovered = currentSet.map(function (set) { + return set[xIndex]; + }); + var xPos = _.max(hovered, function (item) { + return item.attrs.x; + }).attrs.x + barWidth + 8; + + var y = _.map(hovered, function (item) { + return item.attrs.y; + }); + // TODO: 防遮罩算法 + for (var i = 1; i < y.length; i++) { + for (var j = i - 1; j >= 0; j--) { + var overlapped = y.filter(function (item, index) { + return index < i && Math.abs(item - y[i]) < 20; + }); + if (overlapped.length > 0) { + var extent = d3.extent(overlapped); + if (y[i] <= extent[0]) { + y[i] = extent[0] - 20; + } else { + y[i] = extent[1] + 20; + } + } + } + } + hovered.forEach(function (item, columnIndex) { + var yPos = y[columnIndex]; + var valueLabel = '' + values[columnIndex][xIndex][dim.value.index]; + var textWidth = 5 * valueLabel.length + 20; + + var rect = paper.rect(xPos, yPos - 10, textWidth, 20, 2).attr({ + "fill": conf.barColor[columnIndex], + "fill-opacity": 1, + "stroke": "none" + }); + var path = paper.path("M" + xPos + "," + (yPos - 4) + "L" + (xPos - 8) + "," + yPos + "L" + xPos + "," + (yPos + 4) + "V" + yPos + "Z").attr({ + "fill" : conf.barColor[columnIndex], + "stroke" : conf.barColor[columnIndex] + }); + var text = paper.text(xPos + 16, yPos, valueLabel).attr({ + "fill": "#ffffff", + "fill-opacity": 1, + "font-weight": "bold", + "font-size": 12, + "text-anchor": "middle" + }); + tagSet.push(rect, path, text); + }); + + xPos = hovered.reduce(function (pre, cur) { + return pre + cur.attrs.x; + }, 0) / hovered.length + barWidth / 2; + var xLabel = '' + values[columnIndex][xIndex][dim.x.index]; + var textWidth = 6 * xLabel.length + 20; + //axis x rect + var rect = paper.rect(xPos - textWidth / 2, axis.down + 8, textWidth, 20, 2).attr({ + "fill": "#5f5f5f", + "fill-opacity": 1, + "stroke": "none" + }); + // axis x text + var text = paper.text(xPos, axis.down + 18, xLabel).attr({ + "fill": "#ffffff", + "fill-opacity": 1, + "font-weight": "bold", + "font-size": 12, + "text-anchor": "middle" + }); + var arrow = paper.path("M" + (xPos - 4) + "," + (axis.down + 8) + "L" + xPos + "," + axis.down + + "L" + (xPos + 4) + "," + (axis.down + 8) + "H" + xPos + "Z").attr({ + "fill": "#5F5F5F", + "stroke": "#5F5F5F" + }); + tagSet.push(rect, text, arrow); + }; + + var mouseOutBar = function (event) { + var columnIndex = this.data('column'); + var xIndex = this.data('index'); + var currentSet = columnSet.filter(function (set, columnIndex) { + return that.clicked ? that.clickedColumnIndex === columnIndex : true; + }); + tagSet.animate({"opacity": 0}, 1000, function () { + tagSet.remove(); + }); + currentSet.forEach(function (set, columnIndex) { + set.attr({"fill-opacity": 1}); + }); + }; + + values.forEach(function (column, index) { + columnSet[index] = paper.set(); + column.forEach(function (row, i) { + var value = row[dim.value.index]; + var height = that.value(value); + var x = that.x(i); + var rect = paper.rect(x + barWidth * index + conf.gap / 2, height, barWidth, axis.down - height).attr({ + "fill": conf.barColor[index], + "fill-opacity": 1, + "stroke": "none" + }); + rect.data('column', index).data('index', i); + rect.mouseover(mouseOverBar); + rect.mouseout(mouseOutBar); + columnSet[index].push(rect); + }); + }); + }; + + /** + * 绘制图例 + */ + Column.prototype.drawLegend = function () { + var that = this; + var paper = this.canvas; + var legendSet = paper.set(); + var bgSet = paper.set(); + var conf = this.defaults; + var legendArea = conf.legendArea; + var columnCount = this.columnCount; + //legend + var mouseOverLegend = function (event) { + if (legendSet.clicked) { + return; + } + bgSet[this.data('type')].attr({ + "fill-opacity":0.5 + }); + that.fire('legendOver', this.data('type')); + }; + + var mouseOutLegend = function (event) { + if (legendSet.clicked) { + return; + } + bgSet[this.data('type')].attr({"fill-opacity": 0}); + that.fire('legendOut', this.data('type')); + }; + + var clickLegend = function (event) { + if (legendSet.clicked && legendSet.clickedColumn === this.data('type')) { + legendSet.clicked = false; + } else { + legendSet.clicked = true; + legendSet.clickedColumn = this.data('type'); + } + bgSet.attr({"fill-opacity": 0}); + bgSet[this.data('type')].attr({ + "fill-opacity": legendSet.clicked ? 1 : 0 + }); + that.fire('legendClick', legendSet.clicked, this.data('type')); + }; + + var labels = _.keys(this.columns); + for (var i = 0; i < labels.length; i++) { + //底框 + bgSet.push(paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({ + "fill": "#ebebeb", + "fill-opacity": 0, + "stroke": "none" + })); + // 色框 + paper.rect(legendArea[0] + 10 + 3, legendArea[1] + 10 + (20 + 3) * i + 6, 16, 8).attr({ + "fill": conf.barColor[i], + "stroke": "none" + }); + // 文字 + paper.text(legendArea[0] + 10 + 3 + 16 + 8, legendArea[1] + 10 + (20 + 3) * i + 10, labels[i]).attr({ + "fill": "black", + "fill-opacity": 1, + "font-family": "Verdana", + "font-size": 12, + "text-anchor": "start" + }); + // 选框 + var rect = paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({ + "fill": "white", + "fill-opacity": 0, + "stroke": "none" + }).data("type", i); + rect.mouseover(mouseOverLegend); + rect.mouseout(mouseOutLegend); + rect.click(clickLegend); + legendSet.push(rect); + } + }; + + /** + * 绘制柱状图 + * Options: + * + * - `width` 宽度,默认为节点宽度 + * - `typeNames` 指定y轴上数据类目 + * + * Examples: + * ``` + * column.render({"width": 1024}) + * ``` + * @param {Object} options options json object for determin column style. + */ + Column.prototype.render = function (options) { + this.setOptions(options); + this.canvas.clear(); + this.setAxis(); + this.drawAxis(); + this.drawDiagram(); + this.drawLegend(); + }; + /*! + * 导出 + */ + return Column; +}); diff --git a/lib/charts/flow.js b/lib/charts/flow.js index 91a6e95..18e7de3 100644 --- a/lib/charts/flow.js +++ b/lib/charts/flow.js @@ -1,261 +1,261 @@ -/*global EventProxy, d3, Raphael, $ */ -/*! - * Flow的兼容性定义 - */ -;(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];}); - } -})('Flow', function (require) { - var DataV = require('DataV'); - var theme = DataV.Themes; - - /** - * Flow构造函数 - */ - var Flow = DataV.extend(DataV.Chart, { - initialize: function (node, options) { - this.type = "Flow"; - this.node = this.checkContainer(node); - - // Properties - this.font = {}; - - // Canvas - this.defaults.width = 500; - this.defaults.height = 400; - this.defaults.deep = 150; - this.defaults.radius = 50; - this.defaults.xStep = 300; - this.defaults.xStart = 200; - this.defaults.yStart = 50; - - this.setOptions(options); - this.createCanvas(); - } - }); - - Flow.prototype.setSource = function (source) { - var conf = this.defaults; - - this.rawData = source; - this.source = this.remapSource(source); - }; - - Flow.prototype.remapSource = function (data) { - console.log(data); - var dataLength = data.length; - var remapData = []; - - var total; - var i; - for (i = 0; i < dataLength; i++){ - if (!data[i][3]) { - total = data[i][2]; - remapData.push({id: data[i][0], name: data[i][1], value: data[i][2], pid: data[i][3], child: [], deep: 0}); - } else { - remapData.push({id: data[i][0], name: data[i][1], value: data[i][2], pid: data[i][3], child: [], deep: 0}); - } - } - - var conf = this.defaults; - var width = conf.width; - var height = conf.height; - var radius = conf.radius; - var xStep = conf.xStep; - var xStart = conf.xStart; - var yStart = conf.yStart; - var depth = 0; - - for (i = 0; i < dataLength; i++){ - if (remapData[i].pid) { - remapData[i].deep = remapData[remapData[i].pid - 1].deep + 1; - remapData[remapData[i].pid - 1].child.push(remapData[i].id - 1); - if (remapData[i].deep > depth) { - depth = remapData[i].deep; - } - } - // remapData[remapData[i].pid].child.push(remapData[i].id); - } - - this.depth = depth; - radius = Math.min(Math.min((width - xStep * (depth - 1) - xStart * 2) / depth, height*0.55), radius); - console.log("r:" + radius); - for (i = 0; i < dataLength; i++){ - remapData[i].percent = remapData[i].value / total; - remapData[i].radius = radius * remapData[i].percent; - } - return remapData; - // return data; - }; - - Flow.prototype.layout = function () { - var conf = this.defaults; - var width = conf.width; - var height = conf.height; - var xStart = conf.xStart; - var yStart = conf.yStart; - var xStep = conf.xStep; - var remapData = this.source; - - //console.log(this.source); - var circleData = []; - - circleData.push({x: width * 0.24, y: height * 0.42, radius: Math.max(10, remapData[0].radius), deep: remapData[0].deep, name: remapData[0].name, value: remapData[0].value}); - circleData.push({x: width * 0.5, y: height * 0.245, radius: Math.max(10, remapData[1].radius), deep: remapData[1].deep, name: remapData[1].name, value: remapData[1].value}); - circleData.push({x: width * 0.5, y: height * 0.6, radius: Math.max(10, remapData[2].radius), deep: remapData[2].deep, name: remapData[2].name, value: remapData[2].value}); - circleData.push({x: width * 0.72, y: height * 0.5, radius: Math.max(10, remapData[3].radius), deep: remapData[3].deep, name: remapData[3].name, value: remapData[3].value}); - circleData.push({x: width * 0.72, y: height * 0.817, radius: Math.max(10, remapData[4].radius), deep: remapData[4].deep, name: remapData[4].name, value: remapData[4].value}); - - for (i = 0;i < circleData.length; i++) { - console.log(circleData[i].x); - } - - this.circleData = circleData; - }; - - Flow.prototype.getColor = function () { - - var colorMatrix = DataV.getColor(); - - return color; - }; - - // Tree.prototype.getFont = function () { - // //var conf = this.defaults; - - // return DataV.getFont(); - // }; - - Flow.prototype.createCanvas = function () { - var conf = this.defaults; - this.canvas = new Raphael(this.node, conf.width, conf.height); - - this.DOMNode = $(this.canvas.canvas); - var that = this; - this.DOMNode.click(function (event) { - that.trigger("click", event); - }); - this.DOMNode.dblclick(function (event) { - that.trigger("dblclick", event); - }); - - var mousewheel = document.all ? "mousewheel" : "DOMMouseScroll"; - this.DOMNode.bind(mousewheel, function (event) { - that.trigger("mousewheel", event); - }); - - this.DOMNode.bind("contextmenu", function (event) { - that.trigger("contextmenu", event); - }); - - this.DOMNode.delegate("circle", "click", function (event) { - that.trigger("circle_click", event); - }); - - this.DOMNode.delegate("circle", "mouseover", function (event) { - that.trigger("circle_mouseover", event); - }); - - this.DOMNode.delegate("circle", "mouseout", function (event) { - that.trigger("circle_mouseout", event); - }); - - //console.log(this.canvas); - }; - - - Flow.prototype.getLinkPath = function (fx, fy, tx, ty) { - var conf = this.defaults; - - var c1x = fx + (tx - fx) / 1.5; - var c1y = fy; - var c2x = tx - (tx - fx) / 4; - var c2y = ty - (ty - fy) / 2; - - var link_path = [["M", fx, fy], - ["S", c1x, c1y, tx, ty]]; - - return link_path; - }; - - Flow.prototype.generatePaths = function () { - var canvas = this.canvas; - var source = this.source; - var conf = this.defaults; - var radius = conf.radius; - //canvas.clear(); - // var font = this.getFont(); - var font_family = '微软雅黑'; - var font_size = 8; - var depth = this.depth; - var crilceData = this.circleData; - var l = crilceData.length; - var getLinkPath = this.getLinkPath; - - canvas.path().attr({stroke: "#cdcdcd", "stroke-width": 2}).attr({path: getLinkPath(crilceData[0].x, crilceData[0].y, crilceData[1].x, crilceData[1].y)}); - canvas.path().attr({stroke: "#cdcdcd", "stroke-width": 2}).attr({path: getLinkPath(crilceData[0].x, crilceData[0].y, crilceData[2].x, crilceData[2].y)}); - canvas.path().attr({stroke: "#cdcdcd", "stroke-width": 2}).attr({path: getLinkPath(crilceData[2].x, crilceData[2].y, crilceData[3].x, crilceData[3].y)}); - canvas.path().attr({stroke: "#cdcdcd", "stroke-width": 2}).attr({path: getLinkPath(crilceData[2].x, crilceData[2].y, crilceData[4].x, crilceData[4].y)}); - - var i, d; - var thisRadius; - var thisColor; - var titelPath = []; - var valuePath = []; - for (i = 0 ; i < l ; i++) { - d = crilceData[i]; - thisRadius = Math.max(27, d.radius); - if (i === 1) { - thisColor = "#b4e481"; - } else if (i === 4) { - thisColor = "#cd3a19"; - } else { - thisColor = "#ffe79d"; - } - canvas.circle(d.x, d.y, thisRadius) - .attr({"stroke": "none", fill: thisColor}); - titelPath.push(canvas.text(0, 0, d.name).attr({'font-size': 12})); - if (i < l - 2) { - titelPath[i].transform("t" + d.x + "," + (d.y - thisRadius - titelPath[i].getBBox().height/2 - 5)); - } else { - titelPath[i].transform("t" + d.x + "," + (d.y - thisRadius - titelPath[i].getBBox().height/2 - 5)).attr({"text-anchor": "start"}); - } - - if (i < l - 1) { - valuePath.push(canvas.text(d.x, d.y, d.value + "人").attr({'font-size': 12})); - } else { - valuePath.push(canvas.text(d.x, d.y, d.value + "人").attr({fill: "#ffffff", 'font-size': 12})); - } - } - - var n = 0; - - var node; - var num = 0; - - var nodes = canvas.set(); - var path = []; - var textpath = []; - - var tree = this; - }; - - Flow.prototype.render = function (options) { - var st = new Date().getTime(); - this.canvas.clear(); - this.setOptions(options); - this.layout(); - var st2 = new Date().getTime(); - console.log(st2 - st); - - this.generatePaths(); - var et = new Date().getTime(); - console.log(et - st2); - //this.canvas.renderfix(); - }; - - return Flow; -}); +/*global EventProxy, d3, Raphael, $ */ +/*! + * Flow的兼容性定义 + */ +;(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];}); + } +})('Flow', function (require) { + var DataV = require('DataV'); + var theme = DataV.Themes; + + /** + * Flow构造函数 + */ + var Flow = DataV.extend(DataV.Chart, { + initialize: function (node, options) { + this.type = "Flow"; + this.node = this.checkContainer(node); + + // Properties + this.font = {}; + + // Canvas + this.defaults.width = 500; + this.defaults.height = 400; + this.defaults.deep = 150; + this.defaults.radius = 50; + this.defaults.xStep = 300; + this.defaults.xStart = 200; + this.defaults.yStart = 50; + + this.setOptions(options); + this.createCanvas(); + } + }); + + Flow.prototype.setSource = function (source) { + var conf = this.defaults; + + this.rawData = source; + this.source = this.remapSource(source); + }; + + Flow.prototype.remapSource = function (data) { + console.log(data); + var dataLength = data.length; + var remapData = []; + + var total; + var i; + for (i = 0; i < dataLength; i++){ + if (!data[i][3]) { + total = data[i][2]; + remapData.push({id: data[i][0], name: data[i][1], value: data[i][2], pid: data[i][3], child: [], deep: 0}); + } else { + remapData.push({id: data[i][0], name: data[i][1], value: data[i][2], pid: data[i][3], child: [], deep: 0}); + } + } + + var conf = this.defaults; + var width = conf.width; + var height = conf.height; + var radius = conf.radius; + var xStep = conf.xStep; + var xStart = conf.xStart; + var yStart = conf.yStart; + var depth = 0; + + for (i = 0; i < dataLength; i++){ + if (remapData[i].pid) { + remapData[i].deep = remapData[remapData[i].pid - 1].deep + 1; + remapData[remapData[i].pid - 1].child.push(remapData[i].id - 1); + if (remapData[i].deep > depth) { + depth = remapData[i].deep; + } + } + // remapData[remapData[i].pid].child.push(remapData[i].id); + } + + this.depth = depth; + radius = Math.min(Math.min((width - xStep * (depth - 1) - xStart * 2) / depth, height*0.55), radius); + console.log("r:" + radius); + for (i = 0; i < dataLength; i++){ + remapData[i].percent = remapData[i].value / total; + remapData[i].radius = radius * remapData[i].percent; + } + return remapData; + // return data; + }; + + Flow.prototype.layout = function () { + var conf = this.defaults; + var width = conf.width; + var height = conf.height; + var xStart = conf.xStart; + var yStart = conf.yStart; + var xStep = conf.xStep; + var remapData = this.source; + + //console.log(this.source); + var circleData = []; + + circleData.push({x: width * 0.24, y: height * 0.42, radius: Math.max(10, remapData[0].radius), deep: remapData[0].deep, name: remapData[0].name, value: remapData[0].value}); + circleData.push({x: width * 0.5, y: height * 0.245, radius: Math.max(10, remapData[1].radius), deep: remapData[1].deep, name: remapData[1].name, value: remapData[1].value}); + circleData.push({x: width * 0.5, y: height * 0.6, radius: Math.max(10, remapData[2].radius), deep: remapData[2].deep, name: remapData[2].name, value: remapData[2].value}); + circleData.push({x: width * 0.72, y: height * 0.5, radius: Math.max(10, remapData[3].radius), deep: remapData[3].deep, name: remapData[3].name, value: remapData[3].value}); + circleData.push({x: width * 0.72, y: height * 0.817, radius: Math.max(10, remapData[4].radius), deep: remapData[4].deep, name: remapData[4].name, value: remapData[4].value}); + + for (i = 0;i < circleData.length; i++) { + console.log(circleData[i].x); + } + + this.circleData = circleData; + }; + + Flow.prototype.getColor = function () { + + var colorMatrix = DataV.getColor(); + + return color; + }; + + // Tree.prototype.getFont = function () { + // //var conf = this.defaults; + + // return DataV.getFont(); + // }; + + Flow.prototype.createCanvas = function () { + var conf = this.defaults; + this.canvas = new Raphael(this.node, conf.width, conf.height); + + this.DOMNode = $(this.canvas.canvas); + var that = this; + this.DOMNode.click(function (event) { + that.trigger("click", event); + }); + this.DOMNode.dblclick(function (event) { + that.trigger("dblclick", event); + }); + + var mousewheel = document.all ? "mousewheel" : "DOMMouseScroll"; + this.DOMNode.bind(mousewheel, function (event) { + that.trigger("mousewheel", event); + }); + + this.DOMNode.bind("contextmenu", function (event) { + that.trigger("contextmenu", event); + }); + + this.DOMNode.delegate("circle", "click", function (event) { + that.trigger("circle_click", event); + }); + + this.DOMNode.delegate("circle", "mouseover", function (event) { + that.trigger("circle_mouseover", event); + }); + + this.DOMNode.delegate("circle", "mouseout", function (event) { + that.trigger("circle_mouseout", event); + }); + + //console.log(this.canvas); + }; + + + Flow.prototype.getLinkPath = function (fx, fy, tx, ty) { + var conf = this.defaults; + + var c1x = fx + (tx - fx) / 1.5; + var c1y = fy; + var c2x = tx - (tx - fx) / 4; + var c2y = ty - (ty - fy) / 2; + + var link_path = [["M", fx, fy], + ["S", c1x, c1y, tx, ty]]; + + return link_path; + }; + + Flow.prototype.generatePaths = function () { + var canvas = this.canvas; + var source = this.source; + var conf = this.defaults; + var radius = conf.radius; + //canvas.clear(); + // var font = this.getFont(); + var font_family = '微软雅黑'; + var font_size = 8; + var depth = this.depth; + var crilceData = this.circleData; + var l = crilceData.length; + var getLinkPath = this.getLinkPath; + + canvas.path().attr({stroke: "#cdcdcd", "stroke-width": 2}).attr({path: getLinkPath(crilceData[0].x, crilceData[0].y, crilceData[1].x, crilceData[1].y)}); + canvas.path().attr({stroke: "#cdcdcd", "stroke-width": 2}).attr({path: getLinkPath(crilceData[0].x, crilceData[0].y, crilceData[2].x, crilceData[2].y)}); + canvas.path().attr({stroke: "#cdcdcd", "stroke-width": 2}).attr({path: getLinkPath(crilceData[2].x, crilceData[2].y, crilceData[3].x, crilceData[3].y)}); + canvas.path().attr({stroke: "#cdcdcd", "stroke-width": 2}).attr({path: getLinkPath(crilceData[2].x, crilceData[2].y, crilceData[4].x, crilceData[4].y)}); + + var i, d; + var thisRadius; + var thisColor; + var titelPath = []; + var valuePath = []; + for (i = 0 ; i < l ; i++) { + d = crilceData[i]; + thisRadius = Math.max(27, d.radius); + if (i === 1) { + thisColor = "#b4e481"; + } else if (i === 4) { + thisColor = "#cd3a19"; + } else { + thisColor = "#ffe79d"; + } + canvas.circle(d.x, d.y, thisRadius) + .attr({"stroke": "none", fill: thisColor}); + titelPath.push(canvas.text(0, 0, d.name).attr({'font-size': 12})); + if (i < l - 2) { + titelPath[i].transform("t" + d.x + "," + (d.y - thisRadius - titelPath[i].getBBox().height/2 - 5)); + } else { + titelPath[i].transform("t" + d.x + "," + (d.y - thisRadius - titelPath[i].getBBox().height/2 - 5)).attr({"text-anchor": "start"}); + } + + if (i < l - 1) { + valuePath.push(canvas.text(d.x, d.y, d.value + "人").attr({'font-size': 12})); + } else { + valuePath.push(canvas.text(d.x, d.y, d.value + "人").attr({fill: "#ffffff", 'font-size': 12})); + } + } + + var n = 0; + + var node; + var num = 0; + + var nodes = canvas.set(); + var path = []; + var textpath = []; + + var tree = this; + }; + + Flow.prototype.render = function (options) { + var st = new Date().getTime(); + this.canvas.clear(); + this.setOptions(options); + this.layout(); + var st2 = new Date().getTime(); + console.log(st2 - st); + + this.generatePaths(); + var et = new Date().getTime(); + console.log(et - st2); + //this.canvas.renderfix(); + }; + + return Flow; +}); diff --git a/lib/charts/force.js b/lib/charts/force.js index 4378057..68b0815 100644 --- a/lib/charts/force.js +++ b/lib/charts/force.js @@ -1,711 +1,711 @@ -/*global Raphael, d3 */ -/*! - * Force的兼容性定义 - */ -;(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]; - }); - } -})('Force', function (require) { - var DataV = require('DataV'); - /** - * 构造函数 - * @param {Object} node 表示在html的哪个容器中绘制该组件 - * @param {Object} options 为用户自定义的组件的属性,比如画布大小 - */ - var Force = DataV.extend(DataV.Chart, { - initialize: function (node, options) { - this.type = "Force"; - this.node = this.checkContainer(node); - this.net = {}; - this.linkValeMin = 0; - this.linkValeMax = 1; - this.nodeValueMin = 0; - this.nodeValueMax = 1; - this.clicked = false; - this.clickedNum = -1; - this.legendClicked = false; - - // Properties - this.font = {}; - - // Canvas - this.defaults.legend = true; - this.defaults.width = 500; - this.defaults.height = 500; - this.defaults.linkLength = 50; - this.defaults.linkWidth = 2; - this.defaults.classNum = 6; - this.defaults.forceValue = 10; - this.defaults.iterate = 100; - this.defaults.browserName = navigator.appName; - - this.setOptions(options); - this.defaults.charge = -(this.defaults.width + this.defaults.height) / this.defaults.forceValue; - this.legendArea = [20, (this.defaults.height - 20 - this.defaults.classNum * 20), 200, 220]; - if (this.defaults.legend) { - this.xOffset = this.legendArea[2]; - } else { - this.xOffset = 0; - } - - this.createCanvas(); - } - }); - - - /*! - *Strings in CSV to Numbers - * @param {Number} value the value of the elemnts in csv table - */ - Force.prototype._toNum = function (value) { - var type = typeof value; - if (type === "number") { - return value; - } else if (type === "string" && value !== "") { - return parseInt(value, 10); - } else { - return 1; - } - }; - - /** - * Set CSV content to force-directed net - * @param {Array} table the csv table to be rendered - */ - Force.prototype.setSource = function (table) { - //this.net = json; - if (table[0][0] === "Id") { - table = table.slice(1); - } - var nData = []; - var lData = []; - var isNode = true; - var nodeNum; - var that = this; - table.forEach(function (d, i) { - var value; - if (isNode) { - if (d[0] === "Source") { - isNode = false; - nodeNum = i + 1; - } else { - if (d[0] === "") { - throw new Error("ID can not be empty(line:" + (i + 1) + ")."); - } - value = that._toNum(d[2]); - nData[i] = { - name: d[1], - nodeValue: value - }; - if (i === 0) { - that.nodeValueMin = value; - that.nodeValueMax = value; - } - that.nodeValueMin = (value < that.nodeValueMin) ? value : that.nodeValueMin; - that.nodeValueMax = (value > that.nodeValueMax) ? value : that.nodeValueMax; - } - } else { - if (d[0] === "") { - throw new Error("Source can not be empty(line:" + (i + 1) + ")."); - } - if (d[1] === "") { - throw new Error("Target can not be empty(line:" + (i + 1) + ")."); - } - value = that._toNum(d[2]); - lData[i - nodeNum] = { - source: that._toNum(d[0]), - target: that._toNum(d[1]), - value: that._toNum(d[2]) - }; - if (i === nodeNum) { - that.linkValueMin = value; - that.linkValueMax = value; - } - that.linkValueMin = (value < that.linkValueMin) ? value : that.linkValueMin; - that.linkValueMax = (value > that.linkValueMax) ? value : that.linkValueMax; - } - }); - this.net.nodes = nData; - this.net.links = lData; - this.nodeValueMax++; - this.linkValueMax++; - }; - - - /** - * 创建画布 - */ - Force.prototype.createCanvas = function () { - var conf = this.defaults; - this.canvas = new Raphael(this.node, conf.width, conf.height); - //var c = this.canvas.circle(50, 50, 40); - }; - - /** - * 获取节点颜色 - * @param {Number} i 元素类别编号 - * @return {String} 返回颜色值 - */ - Force.prototype.getColor = function (i) { - var color = DataV.getColor(this.classNum); - //var k = color.length * (i - this.nodeValueMin-0.1) / (this.nodeValueMax - this.nodeValueMin); - //if (k < 0) k = 0; - return color[i % color.length][0]; - }; - - /** - * 获取节点的半径 - * @param {Number} value 元素对应的数据值 - * @return {Number} 返回半径值 - */ - Force.prototype.getRadius = function (value) { - var conf = this.defaults; - return 16.0 * (value - this.nodeValueMin) / (this.nodeValueMax - this.nodeValueMin) + 8; - }; - - - /** - * 获取节点透明度 - * @param {Number} value 元素类别编号 - * @return {Number} 返回透明度值 - */ - Force.prototype.getOpacity = function (value) { - return 0.083 * (value - this.linkValueMin) / (this.linkValueMax - this.linkValueMin) + 0.078; - }; - - /** - * update the layout by modify the attributes of nodes and links - */ - Force.prototype.update = function () { - var that = this; - var conf = this.defaults; - var canvas = this.canvas; - - this.nodes = this.canvas.set(); - this.links = this.canvas.set(); - var nodes = this.nodes; - var links = this.links; - var i, j, temp; - this.force.charge(conf.charge).nodes(this.net.nodes).links(this.net.links).start(); - - var nodesData = this.net.nodes; - var linksData = this.net.links; - var nodesNum = nodesData.length; - var linksNum = linksData.length; - var connectMatrix = []; - var linkMatrix = []; - conf.iterate = (nodesNum + linksNum) * 2; - - var onMouseClick = function () { - that.legendClicked = false; - that.underBn.forEach(function (d) { - d.hide(); - d.data('clicked', false); - }); - that.clicked = true; - if (!this.data('clicked')) { - if (conf.browserName !== "Microsoft Internet Explorer") { - that.force.linkDistance(conf.linkLength * 2).charge(conf.charge * 2).start(); - } - that.nodes.forEach(function (d) { - d.data('rect').hide(); - d.data('text').hide(); - d.attr({ - "opacity": 0.2 - }); - d.data('clicked', false); - d.data('showText', false); - }); - that.links.forEach(function (d) { - d.attr({ - 'stroke-opacity': 0.0 - }); - }); - that.clickedNum = this.data('index'); - this.data('clicked', true); - this.data("link").forEach(function (d) { - d.attr({ - "stroke-opacity": d.data('opacity') - }); - }); - this.data("node").forEach(function (d) { - d.attr({ - "opacity": 0.9 - }); - d.data('showText', true); - }); - that.underBn[this.data('colorType')].data('clicked', true).attr('opacity', 1).show(); - } else { - that.clicked = false; - if (conf.browserName !== "Microsoft Internet Explorer") { - that.force.linkDistance(conf.linkLength).charge(conf.charge).start(); - } - nodes.forEach(function (d) { - d.attr({ - "opacity": 0.9 - }); - if (d.data('big')) { - d.data('showText', true); - } else { - d.data('rect').hide(); - d.data('text').hide(); - d.data('showText', false); - } - }); - links.forEach(function (d) { - d.attr({ - 'stroke-opacity': d.data('opacity') - }); - }); - this.data('clicked', false); - that.underBn[this.data('colorType')].hide(); - } - }; - - var onCanvasClick = function () { - that.legendClicked = false; - that.underBn.forEach(function (d) { - d.hide(); - d.data('clicked', false); - }); - that.clicked = false; - if (conf.browserName !== "Microsoft Internet Explorer") { - that.force.linkDistance(conf.linkLength).charge(conf.charge).start(); - } else { - that.force.resume(); - } - nodes.forEach(function (d) { - d.attr({ - "opacity": 0.9 - }); - if (d.data('big')) { - d.data('showText', true); - } else { - d.data('rect').hide(); - d.data('text').hide(); - d.data('showText', false); - } - }); - links.forEach(function (d) { - d.attr({ - 'stroke-opacity': d.data('opacity') - }); - }); - }; - - var topValue = []; - var topId = []; - var topNum = 10; - if (nodesNum < 10) { - topNum = nodesNum; - } - for (i = 0; i < topNum; i++) { - topValue[i] = nodesData[i].nodeValue; - topId[i] = i; - } - for (i = 0; i < topNum; i++) { - for (j = 1; j < topNum - i; j++) { - if (topValue[j] < topValue[j - 1]) { - temp = topValue[j]; - topValue[j] = topValue[j - 1]; - topValue[j - 1] = temp; - temp = topId[j]; - topId[j] = topId[j - 1]; - topId[j - 1] = temp; - } - } - } - //rapheal绘制部分 - for (i = 0; i < nodesNum; i++) { - nodesData[i].x = (conf.width + this.xOffset) / 2; - nodesData[i].y = conf.height / 2; - var n = nodesData[i]; - var k = Math.floor(conf.classNum * (n.nodeValue - this.nodeValueMin) / (this.nodeValueMax - this.nodeValueMin)); - if (k >= conf.classNum) k = conf.classNum - 1; - var radius = this.getRadius(n.nodeValue); - var cnode = canvas.circle(n.x, n.y, radius).attr({ - fill: this.getColor(k), - 'stroke': "#ffffff", - 'opacity': 0.9 - //title: n.name - }); - var nodeText = canvas.text(n.x, n.y - radius, n.name).attr({ - 'opacity': 1, - //'font-family': "微软雅黑", - 'font': '12px Verdana' - }).hide(); - var nodeRect = canvas.rect(n.x, n.y, nodeText.getBBox().width, nodeText.getBBox().height, 2).attr({ - 'fill': "#000000", - 'stroke-opacity': 0, - 'fill-opacity': 0.1 - }).hide(); - cnode.data('r', radius); - cnode.data("name", n.name); - cnode.data('text', nodeText); - cnode.data('rect', nodeRect); - cnode.data('colorType', k); - cnode.data('clicked', false); - cnode.data('index', i); - cnode.data('big', false); - - if (i >= topNum && topValue[0] < nodesData[i].nodeValue) { - topValue[0] = nodesData[i].nodeValue; - topId[0] = i; - for (j = 1; j < topNum; j++) { - if (topValue[j] < topValue[j - 1]) { - temp = topValue[j]; - topValue[j] = topValue[j - 1]; - topValue[j - 1] = temp; - temp = topId[j]; - topId[j] = topId[j - 1]; - topId[j - 1] = temp; - } else { - break; - } - } - } - - nodes.push(cnode); - connectMatrix[i] = []; - linkMatrix[i] = []; - connectMatrix[i].push(nodes[i]); - } - - for (i = 0; i < topNum; i++) { - nodes[topId[i]].data("big", true); - } - - - for (i = 0; i < linksNum; i++) { - var l = linksData[i]; - var clink = canvas.path("M" + l.source.x + "," + l.source.y + "L" + l.target.x + "," + l.target.y).attr({ - 'stroke-width': conf.linkWidth, - 'stroke-opacity': this.getOpacity(l.value) - }).toBack(); - clink.data('opacity', this.getOpacity(l.value)); - links.push(clink); - connectMatrix[l.source.index].push(nodes[l.target.index]); - connectMatrix[l.target.index].push(nodes[l.source.index]); - linkMatrix[l.source.index].push(links[i]); - linkMatrix[l.target.index].push(links[i]); - } - - var background = canvas.rect(0, 0, conf.width, conf.height).attr({ - 'fill': '#ffffff', - 'stroke-opacity': 0 - }).toBack(); - background.click(onCanvasClick); - - nodes.forEach(function (d, i) { - d.data("node", connectMatrix[i]); - d.data("link", linkMatrix[i]); - if (d.data('big')) { - d.data('showText', true); - } else { - d.data('showText', false); - } - d.drag(function (dx, dy) { - d.data('x', this.ox + dx); - d.data('y', this.oy + dy); - }, function () { - that.force.resume(); - this.ox = this.attr("cx"); - this.oy = this.attr("cy"); - d.data('x', this.ox); - d.data('y', this.oy); - d.data('drag', true); - }, function () { - that.force.resume(); - d.data('drag', false); - }); - d.click(onMouseClick); //.mouseup(onmouseup); - d.mouseover(function () { - if (conf.browserName !== "Microsoft Internet Explorer") { - that.force.resume(); - } - this.attr({ - 'r': d.data('r') + 5 - }); - if (!this.data('showText')) { - //this.attr('title', ""); - this.data('showText', true); - this.data('hover', true); - } - if (!that.underBn[this.data('colorType')].data('clicked')) { - that.underBn[this.data('colorType')].attr('opacity', 0.5).show(); - } - }).mouseout(function () { - this.attr({ - 'r': d.data('r') - }); - //this.attr('title', this.data('name')); - if (this.data('hover') && !this.data('clicked')) { - d.data('rect').hide(); - d.data('text').hide(); - this.data('showText', false); - this.data('hover', false); - } - if (!that.underBn[this.data('colorType')].data('clicked')) { - that.underBn[this.data('colorType')].hide(); - } - }); - }); - - }; - - - /** - * 绘制图例 - */ - Force.prototype.legend = function () { - var that = this; - var conf = this.defaults; - var paper = this.canvas; - var legendArea = this.legendArea; - var rectBn = paper.set(); - this.underBn = []; - var underBn = this.underBn; - for (i = 0; i <= conf.classNum - 1; i++) { - //底框 - underBn.push(paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({ - "fill": "#ebebeb", - "stroke": "none", - 'opacity': 1 - }).data('clicked', false).hide()); - //色框 - paper.rect(legendArea[0] + 10 + 3, legendArea[1] + 10 + (20 + 3) * i + 6, 16, 8).attr({ - "fill": that.getColor(i), - "stroke": "none" - }); - //文字 - var min = Math.floor(this.nodeValueMin + i * (this.nodeValueMax - this.nodeValueMin) / conf.classNum); - var max = Math.floor(min + (this.nodeValueMax - this.nodeValueMin) / conf.classNum); - paper.text(legendArea[0] + 10 + 3 + 16 + 8, legendArea[1] + 10 + (20 + 3) * i + 10, min + " ~ " + max).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("clicked", 0); - } - rectBn.forEach(function (d, i) { - d.mouseover(function () { - if (!underBn[i].data('clicked')) { - underBn[i].attr('opacity', 0.5); - underBn[i].show(); - } - }).mouseout(function () { - if (!underBn[i].data('clicked')) { - underBn[i].hide(); - } - }); - d.click(function () { - that.clicked = false; - if (conf.browserName !== "Microsoft Internet Explorer") { - that.force.linkDistance(conf.linkLength).charge(conf.charge).start(); - } - 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) { - that.legendClicked = true; - underBn[i].attr('opacity', 1); - underBn[i].data('clicked', true); - underBn[i].show(); - that.nodes.forEach(function (d) { - if (d.data('colorType') === i) { - d.attr({ - "opacity": 0.9 - }); - d.data('showText', true); - } else { - d.attr({ - "opacity": 0.2 - }); - d.data('rect').hide(); - d.data('text').hide(); - d.data('showText', false); - } - }); - that.links.forEach(function (d) { - d.attr({ - "stroke-opacity": 0 - }); - }); - d.data("clicked", 1); - } else if (d.data("clicked") === 1) { - that.legendClicked = false; - underBn[i].data('clicked', false); - underBn[i].hide(); - d.data("clicked", 0); - that.nodes.forEach(function (d) { - d.attr({ - "opacity": 0.9 - }); - if (d.data('big')) { - d.data('showText', true); - } else { - d.data('rect').hide(); - d.data('text').hide(); - d.data('showText', false); - } - }); - that.links.forEach(function (d) { - d.attr({ - "stroke-opacity": d.data('opacity') - }); - }); - } - }); - }); - }; - - - /** - * create the force-direct layout - */ - Force.prototype.layout = function () { - var conf = this.defaults; - this.force = d3.layout.force().linkDistance(conf.linkLength).size([conf.width + this.xOffset, conf.height]).theta(1.5); - }; - - /** - * update the force-direct layout animation - */ - Force.prototype.animate = function () { - var conf = this.defaults; - var nodes = this.nodes; - var links = this.links; - var tick = 0; - var that = this; - - var nodesData = this.net.nodes; - var linksData = this.net.links; - - this.force.on("tick", function () { - if (conf.browserName !== "Microsoft Internet Explorer" || tick > conf.iterate) { - if (tick % 2 === 0) { - nodes.forEach(function (d, i) { - var margin = d.data('r'); - var nd = nodesData[i]; - if (d.data('drag')) { - nd.x = d.data('x'); - nd.y = d.data('y'); - } - nd.x = (nd.x < margin + that.xOffset) ? (margin + that.xOffset) : nd.x; - nd.x = (nd.x > conf.width - margin) ? conf.width - margin : nd.x; - nd.y = (nd.y < margin) ? margin : nd.y; - nd.y = (nd.y > conf.height - margin) ? conf.height - margin : nd.y; - var bx = d.data('text').getBBox().width / 2; - var by = d.data('text').getBBox().height / 2; - - if (that.clicked) { - var mx = nodesData[that.clickedNum].x; - var my = nodesData[that.clickedNum].y; - var tx, ty; - if (d.data('clicked')) { - - if (conf.browserName !== "Microsoft Internet Explorer") { - nd.x = (conf.width + that.xOffset) / 2; - nd.y = conf.height / 2; - } - d.data('rect').attr({ - 'x': nd.x - bx, - 'y': nd.y + d.data('r') - }); - d.data('text').attr({ - 'x': nd.x, - 'y': nd.y + by + d.data('r') - }); - d.data('rect').show(); - d.data('text').show(); - } else if (d.data('showText')) { - var lx = (nd.x - mx); - var ly = (nd.y - my); - var length = Math.sqrt(lx * lx + ly * ly); - tx = nd.x + bx * lx / length; - ty = nd.y + by * ly / length; - tx = (nd.x < mx) ? tx - d.data('r') : tx + d.data('r'); - ty = (nd.y < my) ? ty - d.data('r') : ty + d.data('r'); - tx = (tx < margin + that.xOffset) ? (margin + that.xOffset) : tx; - tx = (tx > conf.width - margin) ? conf.width - margin : tx; - ty = (ty < margin) ? margin : ty; - ty = (ty > conf.height - margin) ? conf.height - margin : ty; - d.data('rect').attr({ - 'x': tx - bx, - 'y': ty - by - }); - d.data('text').attr({ - 'x': tx, - 'y': ty - }); - d.data('rect').show(); - d.data('text').show(); - } - } else if (d.data('showText')) { - d.data('rect').attr({ - 'x': nd.x - bx, - 'y': nd.y - by + 4 + d.data('r') - }); - d.data('text').attr({ - 'x': nd.x, - 'y': nd.y + 4 + d.data('r') - }); - try { - d.data('rect').show(); - d.data('text').show(); - } catch (e) {} - - } - d.attr({ - 'cx': nd.x, - 'cy': nd.y - }); - }); - links.forEach(function (d, i) { - d.attr('path', "M" + linksData[i].source.x + "," + linksData[i].source.y + "L" + linksData[i].target.x + "," + linksData[i].target.y); - }); - } - }++tick; - }); - }; - - /** - *render the force-directed net on the canvas and keep updating - * @param {Object} options user options - */ - Force.prototype.render = function (options) { - this.setOptions(options); - this.canvas.clear(); - this.layout(); - if (this.defaults.legend) { - this.legend(); - } - this.update(); - this.animate(); - }; - - return Force; +/*global Raphael, d3 */ +/*! + * Force的兼容性定义 + */ +;(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]; + }); + } +})('Force', function (require) { + var DataV = require('DataV'); + /** + * 构造函数 + * @param {Object} node 表示在html的哪个容器中绘制该组件 + * @param {Object} options 为用户自定义的组件的属性,比如画布大小 + */ + var Force = DataV.extend(DataV.Chart, { + initialize: function (node, options) { + this.type = "Force"; + this.node = this.checkContainer(node); + this.net = {}; + this.linkValeMin = 0; + this.linkValeMax = 1; + this.nodeValueMin = 0; + this.nodeValueMax = 1; + this.clicked = false; + this.clickedNum = -1; + this.legendClicked = false; + + // Properties + this.font = {}; + + // Canvas + this.defaults.legend = true; + this.defaults.width = 500; + this.defaults.height = 500; + this.defaults.linkLength = 50; + this.defaults.linkWidth = 2; + this.defaults.classNum = 6; + this.defaults.forceValue = 10; + this.defaults.iterate = 100; + this.defaults.browserName = navigator.appName; + + this.setOptions(options); + this.defaults.charge = -(this.defaults.width + this.defaults.height) / this.defaults.forceValue; + this.legendArea = [20, (this.defaults.height - 20 - this.defaults.classNum * 20), 200, 220]; + if (this.defaults.legend) { + this.xOffset = this.legendArea[2]; + } else { + this.xOffset = 0; + } + + this.createCanvas(); + } + }); + + + /*! + *Strings in CSV to Numbers + * @param {Number} value the value of the elemnts in csv table + */ + Force.prototype._toNum = function (value) { + var type = typeof value; + if (type === "number") { + return value; + } else if (type === "string" && value !== "") { + return parseInt(value, 10); + } else { + return 1; + } + }; + + /** + * Set CSV content to force-directed net + * @param {Array} table the csv table to be rendered + */ + Force.prototype.setSource = function (table) { + //this.net = json; + if (table[0][0] === "Id") { + table = table.slice(1); + } + var nData = []; + var lData = []; + var isNode = true; + var nodeNum; + var that = this; + table.forEach(function (d, i) { + var value; + if (isNode) { + if (d[0] === "Source") { + isNode = false; + nodeNum = i + 1; + } else { + if (d[0] === "") { + throw new Error("ID can not be empty(line:" + (i + 1) + ")."); + } + value = that._toNum(d[2]); + nData[i] = { + name: d[1], + nodeValue: value + }; + if (i === 0) { + that.nodeValueMin = value; + that.nodeValueMax = value; + } + that.nodeValueMin = (value < that.nodeValueMin) ? value : that.nodeValueMin; + that.nodeValueMax = (value > that.nodeValueMax) ? value : that.nodeValueMax; + } + } else { + if (d[0] === "") { + throw new Error("Source can not be empty(line:" + (i + 1) + ")."); + } + if (d[1] === "") { + throw new Error("Target can not be empty(line:" + (i + 1) + ")."); + } + value = that._toNum(d[2]); + lData[i - nodeNum] = { + source: that._toNum(d[0]), + target: that._toNum(d[1]), + value: that._toNum(d[2]) + }; + if (i === nodeNum) { + that.linkValueMin = value; + that.linkValueMax = value; + } + that.linkValueMin = (value < that.linkValueMin) ? value : that.linkValueMin; + that.linkValueMax = (value > that.linkValueMax) ? value : that.linkValueMax; + } + }); + this.net.nodes = nData; + this.net.links = lData; + this.nodeValueMax++; + this.linkValueMax++; + }; + + + /** + * 创建画布 + */ + Force.prototype.createCanvas = function () { + var conf = this.defaults; + this.canvas = new Raphael(this.node, conf.width, conf.height); + //var c = this.canvas.circle(50, 50, 40); + }; + + /** + * 获取节点颜色 + * @param {Number} i 元素类别编号 + * @return {String} 返回颜色值 + */ + Force.prototype.getColor = function (i) { + var color = DataV.getColor(this.classNum); + //var k = color.length * (i - this.nodeValueMin-0.1) / (this.nodeValueMax - this.nodeValueMin); + //if (k < 0) k = 0; + return color[i % color.length][0]; + }; + + /** + * 获取节点的半径 + * @param {Number} value 元素对应的数据值 + * @return {Number} 返回半径值 + */ + Force.prototype.getRadius = function (value) { + var conf = this.defaults; + return 16.0 * (value - this.nodeValueMin) / (this.nodeValueMax - this.nodeValueMin) + 8; + }; + + + /** + * 获取节点透明度 + * @param {Number} value 元素类别编号 + * @return {Number} 返回透明度值 + */ + Force.prototype.getOpacity = function (value) { + return 0.083 * (value - this.linkValueMin) / (this.linkValueMax - this.linkValueMin) + 0.078; + }; + + /** + * update the layout by modify the attributes of nodes and links + */ + Force.prototype.update = function () { + var that = this; + var conf = this.defaults; + var canvas = this.canvas; + + this.nodes = this.canvas.set(); + this.links = this.canvas.set(); + var nodes = this.nodes; + var links = this.links; + var i, j, temp; + this.force.charge(conf.charge).nodes(this.net.nodes).links(this.net.links).start(); + + var nodesData = this.net.nodes; + var linksData = this.net.links; + var nodesNum = nodesData.length; + var linksNum = linksData.length; + var connectMatrix = []; + var linkMatrix = []; + conf.iterate = (nodesNum + linksNum) * 2; + + var onMouseClick = function () { + that.legendClicked = false; + that.underBn.forEach(function (d) { + d.hide(); + d.data('clicked', false); + }); + that.clicked = true; + if (!this.data('clicked')) { + if (conf.browserName !== "Microsoft Internet Explorer") { + that.force.linkDistance(conf.linkLength * 2).charge(conf.charge * 2).start(); + } + that.nodes.forEach(function (d) { + d.data('rect').hide(); + d.data('text').hide(); + d.attr({ + "opacity": 0.2 + }); + d.data('clicked', false); + d.data('showText', false); + }); + that.links.forEach(function (d) { + d.attr({ + 'stroke-opacity': 0.0 + }); + }); + that.clickedNum = this.data('index'); + this.data('clicked', true); + this.data("link").forEach(function (d) { + d.attr({ + "stroke-opacity": d.data('opacity') + }); + }); + this.data("node").forEach(function (d) { + d.attr({ + "opacity": 0.9 + }); + d.data('showText', true); + }); + that.underBn[this.data('colorType')].data('clicked', true).attr('opacity', 1).show(); + } else { + that.clicked = false; + if (conf.browserName !== "Microsoft Internet Explorer") { + that.force.linkDistance(conf.linkLength).charge(conf.charge).start(); + } + nodes.forEach(function (d) { + d.attr({ + "opacity": 0.9 + }); + if (d.data('big')) { + d.data('showText', true); + } else { + d.data('rect').hide(); + d.data('text').hide(); + d.data('showText', false); + } + }); + links.forEach(function (d) { + d.attr({ + 'stroke-opacity': d.data('opacity') + }); + }); + this.data('clicked', false); + that.underBn[this.data('colorType')].hide(); + } + }; + + var onCanvasClick = function () { + that.legendClicked = false; + that.underBn.forEach(function (d) { + d.hide(); + d.data('clicked', false); + }); + that.clicked = false; + if (conf.browserName !== "Microsoft Internet Explorer") { + that.force.linkDistance(conf.linkLength).charge(conf.charge).start(); + } else { + that.force.resume(); + } + nodes.forEach(function (d) { + d.attr({ + "opacity": 0.9 + }); + if (d.data('big')) { + d.data('showText', true); + } else { + d.data('rect').hide(); + d.data('text').hide(); + d.data('showText', false); + } + }); + links.forEach(function (d) { + d.attr({ + 'stroke-opacity': d.data('opacity') + }); + }); + }; + + var topValue = []; + var topId = []; + var topNum = 10; + if (nodesNum < 10) { + topNum = nodesNum; + } + for (i = 0; i < topNum; i++) { + topValue[i] = nodesData[i].nodeValue; + topId[i] = i; + } + for (i = 0; i < topNum; i++) { + for (j = 1; j < topNum - i; j++) { + if (topValue[j] < topValue[j - 1]) { + temp = topValue[j]; + topValue[j] = topValue[j - 1]; + topValue[j - 1] = temp; + temp = topId[j]; + topId[j] = topId[j - 1]; + topId[j - 1] = temp; + } + } + } + //rapheal绘制部分 + for (i = 0; i < nodesNum; i++) { + nodesData[i].x = (conf.width + this.xOffset) / 2; + nodesData[i].y = conf.height / 2; + var n = nodesData[i]; + var k = Math.floor(conf.classNum * (n.nodeValue - this.nodeValueMin) / (this.nodeValueMax - this.nodeValueMin)); + if (k >= conf.classNum) k = conf.classNum - 1; + var radius = this.getRadius(n.nodeValue); + var cnode = canvas.circle(n.x, n.y, radius).attr({ + fill: this.getColor(k), + 'stroke': "#ffffff", + 'opacity': 0.9 + //title: n.name + }); + var nodeText = canvas.text(n.x, n.y - radius, n.name).attr({ + 'opacity': 1, + //'font-family': "微软雅黑", + 'font': '12px Verdana' + }).hide(); + var nodeRect = canvas.rect(n.x, n.y, nodeText.getBBox().width, nodeText.getBBox().height, 2).attr({ + 'fill': "#000000", + 'stroke-opacity': 0, + 'fill-opacity': 0.1 + }).hide(); + cnode.data('r', radius); + cnode.data("name", n.name); + cnode.data('text', nodeText); + cnode.data('rect', nodeRect); + cnode.data('colorType', k); + cnode.data('clicked', false); + cnode.data('index', i); + cnode.data('big', false); + + if (i >= topNum && topValue[0] < nodesData[i].nodeValue) { + topValue[0] = nodesData[i].nodeValue; + topId[0] = i; + for (j = 1; j < topNum; j++) { + if (topValue[j] < topValue[j - 1]) { + temp = topValue[j]; + topValue[j] = topValue[j - 1]; + topValue[j - 1] = temp; + temp = topId[j]; + topId[j] = topId[j - 1]; + topId[j - 1] = temp; + } else { + break; + } + } + } + + nodes.push(cnode); + connectMatrix[i] = []; + linkMatrix[i] = []; + connectMatrix[i].push(nodes[i]); + } + + for (i = 0; i < topNum; i++) { + nodes[topId[i]].data("big", true); + } + + + for (i = 0; i < linksNum; i++) { + var l = linksData[i]; + var clink = canvas.path("M" + l.source.x + "," + l.source.y + "L" + l.target.x + "," + l.target.y).attr({ + 'stroke-width': conf.linkWidth, + 'stroke-opacity': this.getOpacity(l.value) + }).toBack(); + clink.data('opacity', this.getOpacity(l.value)); + links.push(clink); + connectMatrix[l.source.index].push(nodes[l.target.index]); + connectMatrix[l.target.index].push(nodes[l.source.index]); + linkMatrix[l.source.index].push(links[i]); + linkMatrix[l.target.index].push(links[i]); + } + + var background = canvas.rect(0, 0, conf.width, conf.height).attr({ + 'fill': '#ffffff', + 'stroke-opacity': 0 + }).toBack(); + background.click(onCanvasClick); + + nodes.forEach(function (d, i) { + d.data("node", connectMatrix[i]); + d.data("link", linkMatrix[i]); + if (d.data('big')) { + d.data('showText', true); + } else { + d.data('showText', false); + } + d.drag(function (dx, dy) { + d.data('x', this.ox + dx); + d.data('y', this.oy + dy); + }, function () { + that.force.resume(); + this.ox = this.attr("cx"); + this.oy = this.attr("cy"); + d.data('x', this.ox); + d.data('y', this.oy); + d.data('drag', true); + }, function () { + that.force.resume(); + d.data('drag', false); + }); + d.click(onMouseClick); //.mouseup(onmouseup); + d.mouseover(function () { + if (conf.browserName !== "Microsoft Internet Explorer") { + that.force.resume(); + } + this.attr({ + 'r': d.data('r') + 5 + }); + if (!this.data('showText')) { + //this.attr('title', ""); + this.data('showText', true); + this.data('hover', true); + } + if (!that.underBn[this.data('colorType')].data('clicked')) { + that.underBn[this.data('colorType')].attr('opacity', 0.5).show(); + } + }).mouseout(function () { + this.attr({ + 'r': d.data('r') + }); + //this.attr('title', this.data('name')); + if (this.data('hover') && !this.data('clicked')) { + d.data('rect').hide(); + d.data('text').hide(); + this.data('showText', false); + this.data('hover', false); + } + if (!that.underBn[this.data('colorType')].data('clicked')) { + that.underBn[this.data('colorType')].hide(); + } + }); + }); + + }; + + + /** + * 绘制图例 + */ + Force.prototype.legend = function () { + var that = this; + var conf = this.defaults; + var paper = this.canvas; + var legendArea = this.legendArea; + var rectBn = paper.set(); + this.underBn = []; + var underBn = this.underBn; + for (i = 0; i <= conf.classNum - 1; i++) { + //底框 + underBn.push(paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({ + "fill": "#ebebeb", + "stroke": "none", + 'opacity': 1 + }).data('clicked', false).hide()); + //色框 + paper.rect(legendArea[0] + 10 + 3, legendArea[1] + 10 + (20 + 3) * i + 6, 16, 8).attr({ + "fill": that.getColor(i), + "stroke": "none" + }); + //文字 + var min = Math.floor(this.nodeValueMin + i * (this.nodeValueMax - this.nodeValueMin) / conf.classNum); + var max = Math.floor(min + (this.nodeValueMax - this.nodeValueMin) / conf.classNum); + paper.text(legendArea[0] + 10 + 3 + 16 + 8, legendArea[1] + 10 + (20 + 3) * i + 10, min + " ~ " + max).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("clicked", 0); + } + rectBn.forEach(function (d, i) { + d.mouseover(function () { + if (!underBn[i].data('clicked')) { + underBn[i].attr('opacity', 0.5); + underBn[i].show(); + } + }).mouseout(function () { + if (!underBn[i].data('clicked')) { + underBn[i].hide(); + } + }); + d.click(function () { + that.clicked = false; + if (conf.browserName !== "Microsoft Internet Explorer") { + that.force.linkDistance(conf.linkLength).charge(conf.charge).start(); + } + 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) { + that.legendClicked = true; + underBn[i].attr('opacity', 1); + underBn[i].data('clicked', true); + underBn[i].show(); + that.nodes.forEach(function (d) { + if (d.data('colorType') === i) { + d.attr({ + "opacity": 0.9 + }); + d.data('showText', true); + } else { + d.attr({ + "opacity": 0.2 + }); + d.data('rect').hide(); + d.data('text').hide(); + d.data('showText', false); + } + }); + that.links.forEach(function (d) { + d.attr({ + "stroke-opacity": 0 + }); + }); + d.data("clicked", 1); + } else if (d.data("clicked") === 1) { + that.legendClicked = false; + underBn[i].data('clicked', false); + underBn[i].hide(); + d.data("clicked", 0); + that.nodes.forEach(function (d) { + d.attr({ + "opacity": 0.9 + }); + if (d.data('big')) { + d.data('showText', true); + } else { + d.data('rect').hide(); + d.data('text').hide(); + d.data('showText', false); + } + }); + that.links.forEach(function (d) { + d.attr({ + "stroke-opacity": d.data('opacity') + }); + }); + } + }); + }); + }; + + + /** + * create the force-direct layout + */ + Force.prototype.layout = function () { + var conf = this.defaults; + this.force = d3.layout.force().linkDistance(conf.linkLength).size([conf.width + this.xOffset, conf.height]).theta(1.5); + }; + + /** + * update the force-direct layout animation + */ + Force.prototype.animate = function () { + var conf = this.defaults; + var nodes = this.nodes; + var links = this.links; + var tick = 0; + var that = this; + + var nodesData = this.net.nodes; + var linksData = this.net.links; + + this.force.on("tick", function () { + if (conf.browserName !== "Microsoft Internet Explorer" || tick > conf.iterate) { + if (tick % 2 === 0) { + nodes.forEach(function (d, i) { + var margin = d.data('r'); + var nd = nodesData[i]; + if (d.data('drag')) { + nd.x = d.data('x'); + nd.y = d.data('y'); + } + nd.x = (nd.x < margin + that.xOffset) ? (margin + that.xOffset) : nd.x; + nd.x = (nd.x > conf.width - margin) ? conf.width - margin : nd.x; + nd.y = (nd.y < margin) ? margin : nd.y; + nd.y = (nd.y > conf.height - margin) ? conf.height - margin : nd.y; + var bx = d.data('text').getBBox().width / 2; + var by = d.data('text').getBBox().height / 2; + + if (that.clicked) { + var mx = nodesData[that.clickedNum].x; + var my = nodesData[that.clickedNum].y; + var tx, ty; + if (d.data('clicked')) { + + if (conf.browserName !== "Microsoft Internet Explorer") { + nd.x = (conf.width + that.xOffset) / 2; + nd.y = conf.height / 2; + } + d.data('rect').attr({ + 'x': nd.x - bx, + 'y': nd.y + d.data('r') + }); + d.data('text').attr({ + 'x': nd.x, + 'y': nd.y + by + d.data('r') + }); + d.data('rect').show(); + d.data('text').show(); + } else if (d.data('showText')) { + var lx = (nd.x - mx); + var ly = (nd.y - my); + var length = Math.sqrt(lx * lx + ly * ly); + tx = nd.x + bx * lx / length; + ty = nd.y + by * ly / length; + tx = (nd.x < mx) ? tx - d.data('r') : tx + d.data('r'); + ty = (nd.y < my) ? ty - d.data('r') : ty + d.data('r'); + tx = (tx < margin + that.xOffset) ? (margin + that.xOffset) : tx; + tx = (tx > conf.width - margin) ? conf.width - margin : tx; + ty = (ty < margin) ? margin : ty; + ty = (ty > conf.height - margin) ? conf.height - margin : ty; + d.data('rect').attr({ + 'x': tx - bx, + 'y': ty - by + }); + d.data('text').attr({ + 'x': tx, + 'y': ty + }); + d.data('rect').show(); + d.data('text').show(); + } + } else if (d.data('showText')) { + d.data('rect').attr({ + 'x': nd.x - bx, + 'y': nd.y - by + 4 + d.data('r') + }); + d.data('text').attr({ + 'x': nd.x, + 'y': nd.y + 4 + d.data('r') + }); + try { + d.data('rect').show(); + d.data('text').show(); + } catch (e) {} + + } + d.attr({ + 'cx': nd.x, + 'cy': nd.y + }); + }); + links.forEach(function (d, i) { + d.attr('path', "M" + linksData[i].source.x + "," + linksData[i].source.y + "L" + linksData[i].target.x + "," + linksData[i].target.y); + }); + } + }++tick; + }); + }; + + /** + *render the force-directed net on the canvas and keep updating + * @param {Object} options user options + */ + Force.prototype.render = function (options) { + this.setOptions(options); + this.canvas.clear(); + this.layout(); + if (this.defaults.legend) { + this.legend(); + } + this.update(); + this.animate(); + }; + + return Force; }); \ No newline at end of file diff --git a/lib/charts/line.js b/lib/charts/line.js index ce35db0..22f9330 100644 --- a/lib/charts/line.js +++ b/lib/charts/line.js @@ -1,589 +1,589 @@ -/*global Raphael, _, $ */ -;(function (name, definition) { - if (typeof define === 'function') { - define(definition); - } else { - this[name] = definition(function (id) { return this[id];}); - } -})('Line', function (require) { - var DataV = require('DataV'); - var theme = DataV.Themes; - - /** - * Line构造函数,继承自Chart - * Options: - * - * - `width` 数字,画布宽度,默认为960,表示图片高960px - * - `height` 数字,画布高度,默认为500 - * - `margin` 数组,这个折线图在画布中四周的留白,长度为4,分别代表[top, right, bottom, left], 默认值为[10, 40, 40, 40] - * - `title` 字符串,一级标题, 默认值为null - * - `subtitle` 字符串,副标题, 默认值为null - * - `imagePath 字符串,折线图提供背景图片加载路径,默认值为null - * - `clickMode 布尔值,是否使用默认的点击事件,默认点击事件为点击折线即选中当前折线,点击空白部分取消选中,默认值为true - * - `hoverMode 布尔值,是否使用默认的悬停事件,默认悬停事件为当前悬停所在折线高亮,如果在节点上悬停则节点高亮,同一X维度节点也高亮, 默认值为true - * - `nodeMode 布尔值,是否需要显示节点,默认值为true - * - `lineSize 数字,折线粗细,默认值为2 - * - `gridSize 数字,网格线粗细,默认值为1 - * - `nodeRadius 数字,节点圆形半径,默认值为2 - * - `hasXAxis 布尔值,是否需要绘制x坐标轴,默认值为true - * - `hasYAxis 布尔值,是否需要绘制y坐标轴,默认值为true - * - `xAxisTick 数字,x轴刻度的跨度,默认值为10 - * - `yAxisTick 数字,y轴刻度的跨度,默认值为10 - * - `xAxisOrient 字符串,x坐标轴位置,可选值为"top"和"bottom",默认值为"bottom" - * - `yAxisOrient 字符串,x坐标轴位置,可选值为"left"和"right",默认值为"left" - * - `xAxisPadding 数字,x轴与x轴刻度值说明文字的距离,默认值为10 - * - `yAxisPadding 数字,y轴与y轴刻度值说明文字的距离,默认值为10 - * - `xAxisFontSize 数字,x轴说明文字字号,默认值为10 - * - `yAxisFontSize 数字,y轴说明文字字号,默认值为10 - * - `xAxisStartDx 数字,x轴刻度起始位置与坐标轴最左端水平距离,默认值为20 - * - `yAxisStartDy 数字,y轴刻度起始位置与坐标轴最下端垂直距离,默认值为10 - * - `xAxisEndDx 数字,x轴刻度起始位置与坐标轴最右端水平距离,默认值为10 - * - `yAxisEndDy 数字,y轴刻度起始位置与坐标轴最上端水平距离,默认值为10 - * - `textLean 布尔值,是否将x轴说明文字倾斜摆放,默认值为false - * - `hasXGrid 布尔值,是否需要绘制与x坐标轴平行的网格线,默认值为true - * - `hasYGrid 布尔值,是否需要绘制与x坐标轴平行的网格线,默认值为true - * - `backgroundColor 字符串,折线图背景填充色,默认值为"#fff" - * - `titleColor 字符串,标题文字颜色,默认值为"#000" - * - `subtitleColor 字符串,副标题文字颜色,默认值为"#000" - * - `AxisColor 字符串,坐标轴填充颜色,默认值为"#000" - * - `gridColor 字符串,网格线填充颜色,默认值为"#000" - * - `unchosen0pacity 数字,当有折线被选中时,其他淡出折线的透明度,默认值为0.15 - * - `grid0pacity 数字,网格线透明度,默认值为0.1 - * - `chosenGrid0pacity 数字,高亮网格线透明度,默认值为0.5 - - * Create line in a dom node with id "chart", width is 500px; height is 600px; - * Examples: - * ``` - * var line = new Line("chart", {"width": 500, "height": 600}); - * ``` - * @param {Mix} node The dom node or dom node Id - * @param {Object} options options json object for determin line style. - */ - var Line = DataV.extend(DataV.Chart, { - initialize: function (node, options) { - this.type = "Line"; - this.node = this.checkContainer(node); - - /** - * 线纬度 - */ - this.dimension.line = { - type: "string", - required: true, - index: 0 - }; - /** - * 值纬度 - */ - this.dimension.x = { - type: "string", - required: true, - index: 1 - }; - /** - * 值纬度 - */ - this.dimension.value = { - type: "number", - required: true, - index: 2 - }; - - this.defaults.width = 960; - this.defaults.height = 500; - - //Properties - this.defaults.margin = [10, 40, 40, 40]; - this.defaults.title = null; - this.defaults.subtitle = null; - this.defaults.imagePath = null; - - this.defaults.clickMode = true; - this.defaults.hoverMode = true; - - //绘制属性 - this.defaults.lineSize = 2; - this.defaults.gridSize = 1; - this.defaults.nodeMode = true; - this.defaults.nodeRadius = 2; - this.defaults.hasXAxis = true; - this.defaults.hasYAxis = true; - this.defaults.xAxisTick = 10; - this.defaults.yAxisTick = 4; - this.defaults.xAxisOrient = "bottom"; - this.defaults.yAxisOrient = "left"; - this.defaults.xAxisPadding = 10; - this.defaults.yAxisPadding = 10; - this.defaults.xAxisFontSize = 10; - this.defaults.yAxisFontSize = 10; - this.defaults.xAxisStartDx = 20; - this.defaults.yAxisStartDy = 10; - this.defaults.xAxisEndDx = 10; - this.defaults.yAxisEndDy = 10; - - this.defaults.textLean = false; - - this.defaults.hasXGrid = true; - this.defaults.hasYGrid = true; - - //Color - this.defaults.backgroundColor = "#fff"; - this.defaults.titleColor = "#000"; - this.defaults.subtitleColor = "#000"; - this.defaults.AxisColor = "#000"; - this.defaults.gridColor = "#000"; - this.defaults.unchosen0pacity = 0.15; - this.defaults.grid0pacity = 0.1; - this.defaults.chosenGrid0pacity = 0.5; - - //设置用户指定属性 - this.setOptions(options); - - //创建画布 - this.createCanvas(); - } - }); - - /** - * 创建画布 - */ - Line.prototype.createCanvas = function () { - var conf = this.defaults; - - this.canvas = new Raphael(this.node, conf.width, conf.height); - - this.canvasF = this.node; - - var canvasStyle = this.canvasF.style; - canvasStyle.position = "relative"; - this.floatTag = DataV.FloatTag()(this.canvasF); - this.floatTag.css({ - "visibility": "hidden" - }); - }; - - /** - * 对原始数据进行处理 - */ - Line.prototype.setSource = function (source, map) { - map = this.map(map); - var dataTable; - if (DataV.detect(source) === 'Table_WITH_HEAD') { - dataTable = DataV.collectionify(source); - } else { - dataTable = source; - } - - var lines = _.groupBy(dataTable, map.line); - var linesData = []; - - var max, min; - var maxList = [], minList = []; - this.maxLength = Math.max.apply(null, _.map(lines, function (line) { - return line.length; - })); - - var titles; - _.forEach(lines, function (points, name) { - // initialize the nodes of line - var line = {name: name, id: name, data: [], tags: []}; - line.data = _.pluck(points, map.value); - titles = _.pluck(points, map.x); - linesData.push(line); - maxList[name] = Math.max.apply(null, line.data); - minList[name] = Math.min.apply(null, line.data); - }); - - var conf = this.defaults; - var margin = conf.margin; - this.xWidth = conf.width - margin[1] - margin[3]; - this.yHeight = conf.height - margin[0] - margin[2]; - - this.titles = titles; - this.linesData = linesData; - this.max = Math.max.apply(null, _.values(maxList)); - this.min = Math.min.apply(null, _.values(minList)); - this.maxList = maxList; - this.minList = minList; - - this.chosen = false; - this.chosenNum = -1; - }; - - /** - * 获取颜色函数 - * @return {function} DataV根据主题获取随机离散色函数 - */ - Line.prototype.getColor = function () { - var colorFunction = DataV.getDiscreteColor(); - return colorFunction; - }; - - /** - * 绘制X轴 - */ - Line.prototype.setXaxis = function (titles) { - var conf = this.defaults; - var canvas = this.canvas; - - if (conf.hasXAxis) { - var lineSize = conf.lineSize; - var gridSize = conf.gridSize; - var axisColor = conf.AxisColor; - var gridColor = conf.gridColor; - var grid0pacity = conf.grid0pacity; - var xAxisPadding = conf.xAxisPadding; - var margin = conf.margin; - var xAxisStartDx = conf.xAxisStartDx; - var xAxisEndDx = conf.xAxisEndDx; - var startX = margin[3] + xAxisStartDx; - var startY = conf.height - margin[2]; - var xWidth = this.xWidth; - var xDataArea = xWidth - xAxisStartDx - xAxisEndDx; - var maxLength = this.maxLength; - var yHeight = this.yHeight; - var fontSize = conf.xAxisFontSize; - var textLean = conf.textLean; - - var tickLength; - if (conf.hasXGrid) { - tickLength = yHeight; - } else { - tickLength = 5; - } - - var tick = conf.xAxisTick; - var tickStep; - if (tick % maxLength === 0) { - tickStep = xDataArea / (tick - 1); - } else { - tickStep = xDataArea / (this.maxLength - 1); - tick = this.maxLength; - } - - var xAxis = canvas.set(); - var xGrid = canvas.set(); - var xAxisText = canvas.set(); - xAxis.push(canvas.path("M" + (startX - xAxisStartDx) + "," + startY + "L" + (xWidth + margin[3]) + "," + startY)); - - var l = titles.length; - for (var i = 0; i < tick; i++) { - xGrid.push(canvas.path("M" + (startX + i * tickStep) + "," + startY + "L" + (startX + i * tickStep) + "," + (startY - tickLength))); - if (i < l) { - var thisText = canvas.text((startX + i * tickStep), startY + xAxisPadding, titles[i]); - if (textLean) { - var d = thisText.getBBox().width / 2; - var angle = 45 / 360 * Math.PI; - thisText.transform("r45t" + Math.cos(angle) * d + "," + Math.sin(angle) * d); - } - xAxisText.push(thisText); - } - } - - xAxis.attr({fill: axisColor, "stroke-width": lineSize}); - xGrid.attr({"stroke": gridColor, "stroke-width": gridSize, "stroke-opacity": grid0pacity}); - xAxisText.attr({"font-size": fontSize}); - this.xAxis = xAxis; - this.xGrid = xGrid; - this.xAxisText = xAxisText; - this.xTick = xDataArea / (maxLength - 1); - } - }; - - /** - * 绘制Y轴 - */ - Line.prototype.setYaxis = function () { - var conf = this.defaults; - var canvas = this.canvas; - - if (conf.hasYAxis) { - var lineSize = conf.lineSize; - var gridSize = conf.gridSize; - var axisColor = conf.AxisColor; - var gridColor = conf.gridColor; - var grid0pacity = conf.grid0pacity; - var margin = conf.margin; - var yAxisStartDy = conf.yAxisStartDy; - var yAxisEndDy = conf.yAxisEndDy; - var yHeight = this.yHeight; - var yDataArea = this.yHeight - yAxisStartDy - yAxisEndDy; - var xWidth = this.xWidth; - var startX = margin[3]; - var startY = margin[0] + yHeight - yAxisStartDy; - var yAxisPadding = conf.yAxisPadding; - var max = this.max; - var min = this.min; - var fontSize = conf.yAxisFontSize; - - var tick = conf.yAxisTick; - var tickStep = yDataArea / (tick - 1); - - var d = (max - min) % (tick - 1); - if (d !== 0) { - max = max + (tick - 1) - d; - } - d = (max - min) / (tick - 1); - - var tickLength; - if (conf.hasYGrid) { - tickLength = xWidth; - } else { - tickLength = 5; - } - - var yAxis = canvas.set(); - var yGrid = canvas.set(); - var yAxisText = canvas.set(); - yAxis.push(canvas.path("M" + startX + "," + (startY + yAxisStartDy) + "L" + startX + "," + margin[0])); - - for (var i = 0; i < tick; i++) { - yGrid.push(canvas.path("M" + startX + "," + (startY - i * tickStep) + "L" + (startX + tickLength) + "," + (startY - i * tickStep))); - yAxisText.push(canvas.text(startX - yAxisPadding, (startY - i * tickStep), (min + i * d))); - } - - yAxis.attr({fill: axisColor, "stroke-width": lineSize}); - yGrid.attr({"stroke": gridColor, "stroke-width": gridSize, "stroke-opacity": grid0pacity}); - yAxisText.attr({"font-size": fontSize}); - this.yAxis = yAxis; - this.yGrid = yGrid; - this.yAxisText = yAxisText; - this.yDataArea = yDataArea; - - this.yMatchNum = tickStep / d; - this.y0 = startY; - } - }; - - /** - * 绘制背景 - */ - Line.prototype.setBackground = function () { - var conf = this.defaults; - var canvas = this.canvas; - var backgroundColor = conf.backgroundColor; - var imagePath = conf.imagePath; - var xWidth = this.xWidth; - var yHeight = this.yHeight; - var yAxisStartDy = conf.yAxisStartDy; - var x0 = conf.margin[3]; - var y0 = this.y0 + yAxisStartDy; - - var rect; - if (imagePath !== null) { - rect = canvas.image(imagePath, x0, (y0 - yHeight), xWidth, yHeight); - } else { - rect = canvas.rect(x0, (y0 - yHeight), xWidth, yHeight).attr({"fill": backgroundColor,"fill-opacity": 0, "stroke": "none"}); - } - - rect.toBack(); - this.background = rect; - }; - - /** - * 渲染折线图 - * Examples: - * ``` - * var line = new Line("chart"); - * line.setSource(source); - * line.render(); - * ``` - * @param {object} options options json object for determin line style. - */ - Line.prototype.render = function () { - this.canvas.clear(); - this.createCanvas(); - var conf = this.defaults; - var linesData = this.linesData; - var nodesList = []; - - this.setXaxis(this.titles); - this.setYaxis(); - - this.setBackground(); - - var canvas = this.canvas; - var nodeMode = conf.nodeMode; - var getColor = this.getColor(); - var lineSize = conf.lineSize; - var min = this.min; - var xAxisStartDx = conf.xAxisStartDx; - var xTick = this.xTick; - var yMatchNum = this.yMatchNum; - var x0 = conf.margin[3] + xAxisStartDx; - var y0 = this.y0; - var radius = conf.nodeRadius; - - var lines = canvas.set(); - linesData.forEach(function (d, i) { - var nodeData = d.data; - var nodes = canvas.set(); - var color = getColor(i); - var linePath = "M"; - - for (var j = 0, l = nodeData.length; j < l; j++) { - var x = x0 + xTick * (j); - var y = y0 - ((nodeData[j] - min) * yMatchNum); - - linePath = linePath + x + "," + y; - - if (j < l - 1) { - linePath = linePath + "L"; - } - - if (nodeMode) { - var thisNode = canvas.circle(x, y, radius * 2).attr({fill: color, "stroke": "none"}); - thisNode.data('num', j); - thisNode.data('lineNum', i); - thisNode.data('data', nodeData[j]); - nodes.push(thisNode); - } - } - - lines.push(canvas.path(linePath).attr({"stroke": color, "stroke-width": lineSize}).data('num', i)); - if (nodeMode) { - nodesList.push(nodes.toFront()); - } - }); - - this.lines = lines; - this.nodesList = nodesList; - - this.interactive(); - }; - - /** - * 添加交互选项 - */ - Line.prototype.interactive = function () { - var that = this; - var conf = this.defaults; - var hoverMode = conf.hoverMode; - var clickMode = conf.clickMode; - var nodeMode = conf.nodeMode; - var chosen = this.chosen; - var chosenNum = this.chosenNum; - var lines = this.lines; - var uo = conf.unchosen0pacity; - var nodesList = this.nodesList; - var xGrid = this.xGrid; - var grid0pacity = conf.grid0pacity; - var cgo = conf.chosenGrid0pacity; - var xAxisText = this.xAxisText; - var xAxisFontSize = conf.xAxisFontSize; - - var highLight = function (num) { - var line = lines[num]; - lines.attr({"stroke-opacity": uo}); - - nodesList.forEach(function (d) { - d.attr({"fill-opacity": uo}); - }); - - line.attr({"stroke-opacity": 1}).toFront(); - nodesList[num].attr({"fill-opacity": 1}).toFront(); - }; - - var unhighLinght = function () { - lines.forEach(function (d) { - d.attr({"stroke-opacity": 1}); - }); - nodesList.forEach(function (d) { - d.attr({"fill-opacity": 1}); - }); - }; - - var background = this.background; - - if (clickMode){ - background.click(function () { - if (chosen) { - unhighLinght(); - chosen = false; - that.chosen = chosen; - } - }); - } - - var floatTag = this.floatTag; - $(this.node).append(this.floatTag); - // if (hoverMode) { - // background.mouseover(function () { - - // }).mouseout(function () { - // floatTag.css({"visibility" : "hidden"}); - // }); - // } - - lines.forEach(function (d) { - if (hoverMode) { - d.mouseover(function () { - if (!chosen) { - highLight(d.data('num')); - } - }).mouseout(function () { - if (!chosen) { - unhighLinght(); - } - }); - } - - if (clickMode){ - d.click(function () { - chosenNum = d.data('num'); - highLight(chosenNum); - - chosen = true; - that.chosen = chosen; - }); - } - }); - - if (nodeMode){ - var radius = conf.nodeRadius; - - nodesList.forEach(function (d) { - d.forEach (function (d) { - if (hoverMode) { - var nodeNum = d.data('num'); - d.mouseover(function () { - d.animate({r: (radius + 2) * 2}, 100); - xGrid[nodeNum].animate({'stroke-opacity': cgo}, 100); - xAxisText[nodeNum].animate({'font-size': xAxisFontSize * 2}, 100); - floatTag.html('
' + d.data('data') + '
'); - floatTag.css({"visibility": "visible"}); - if (!chosen) { - highLight(d.data("lineNum")); - nodesList.forEach(function (d) { - if (nodeNum < d.length) { - d[nodeNum].attr({"fill-opacity": 1}); - } - }); - } - }).mouseout(function () { - d.animate({r: radius * 2}, 100); - xGrid[nodeNum].animate({'stroke-opacity': grid0pacity}, 100); - xAxisText[nodeNum].animate({'font-size': xAxisFontSize}, 100); - floatTag.css({"visibility": "hidden"}); - if (!chosen) { - unhighLinght(); - } - }); - } - - if (clickMode){ - d.click(function () { - chosenNum = d.data('lineNum'); - highLight(chosenNum); - - chosen = true; - that.chosen = chosen; - }); - } - }); - }); - } - }; - - return Line; +/*global Raphael, _, $ */ +;(function (name, definition) { + if (typeof define === 'function') { + define(definition); + } else { + this[name] = definition(function (id) { return this[id];}); + } +})('Line', function (require) { + var DataV = require('DataV'); + var theme = DataV.Themes; + + /** + * Line构造函数,继承自Chart + * Options: + * + * - `width` 数字,画布宽度,默认为960,表示图片高960px + * - `height` 数字,画布高度,默认为500 + * - `margin` 数组,这个折线图在画布中四周的留白,长度为4,分别代表[top, right, bottom, left], 默认值为[10, 40, 40, 40] + * - `title` 字符串,一级标题, 默认值为null + * - `subtitle` 字符串,副标题, 默认值为null + * - `imagePath 字符串,折线图提供背景图片加载路径,默认值为null + * - `clickMode 布尔值,是否使用默认的点击事件,默认点击事件为点击折线即选中当前折线,点击空白部分取消选中,默认值为true + * - `hoverMode 布尔值,是否使用默认的悬停事件,默认悬停事件为当前悬停所在折线高亮,如果在节点上悬停则节点高亮,同一X维度节点也高亮, 默认值为true + * - `nodeMode 布尔值,是否需要显示节点,默认值为true + * - `lineSize 数字,折线粗细,默认值为2 + * - `gridSize 数字,网格线粗细,默认值为1 + * - `nodeRadius 数字,节点圆形半径,默认值为2 + * - `hasXAxis 布尔值,是否需要绘制x坐标轴,默认值为true + * - `hasYAxis 布尔值,是否需要绘制y坐标轴,默认值为true + * - `xAxisTick 数字,x轴刻度的跨度,默认值为10 + * - `yAxisTick 数字,y轴刻度的跨度,默认值为10 + * - `xAxisOrient 字符串,x坐标轴位置,可选值为"top"和"bottom",默认值为"bottom" + * - `yAxisOrient 字符串,x坐标轴位置,可选值为"left"和"right",默认值为"left" + * - `xAxisPadding 数字,x轴与x轴刻度值说明文字的距离,默认值为10 + * - `yAxisPadding 数字,y轴与y轴刻度值说明文字的距离,默认值为10 + * - `xAxisFontSize 数字,x轴说明文字字号,默认值为10 + * - `yAxisFontSize 数字,y轴说明文字字号,默认值为10 + * - `xAxisStartDx 数字,x轴刻度起始位置与坐标轴最左端水平距离,默认值为20 + * - `yAxisStartDy 数字,y轴刻度起始位置与坐标轴最下端垂直距离,默认值为10 + * - `xAxisEndDx 数字,x轴刻度起始位置与坐标轴最右端水平距离,默认值为10 + * - `yAxisEndDy 数字,y轴刻度起始位置与坐标轴最上端水平距离,默认值为10 + * - `textLean 布尔值,是否将x轴说明文字倾斜摆放,默认值为false + * - `hasXGrid 布尔值,是否需要绘制与x坐标轴平行的网格线,默认值为true + * - `hasYGrid 布尔值,是否需要绘制与x坐标轴平行的网格线,默认值为true + * - `backgroundColor 字符串,折线图背景填充色,默认值为"#fff" + * - `titleColor 字符串,标题文字颜色,默认值为"#000" + * - `subtitleColor 字符串,副标题文字颜色,默认值为"#000" + * - `AxisColor 字符串,坐标轴填充颜色,默认值为"#000" + * - `gridColor 字符串,网格线填充颜色,默认值为"#000" + * - `unchosen0pacity 数字,当有折线被选中时,其他淡出折线的透明度,默认值为0.15 + * - `grid0pacity 数字,网格线透明度,默认值为0.1 + * - `chosenGrid0pacity 数字,高亮网格线透明度,默认值为0.5 + + * Create line in a dom node with id "chart", width is 500px; height is 600px; + * Examples: + * ``` + * var line = new Line("chart", {"width": 500, "height": 600}); + * ``` + * @param {Mix} node The dom node or dom node Id + * @param {Object} options options json object for determin line style. + */ + var Line = DataV.extend(DataV.Chart, { + initialize: function (node, options) { + this.type = "Line"; + this.node = this.checkContainer(node); + + /** + * 线纬度 + */ + this.dimension.line = { + type: "string", + required: true, + index: 0 + }; + /** + * 值纬度 + */ + this.dimension.x = { + type: "string", + required: true, + index: 1 + }; + /** + * 值纬度 + */ + this.dimension.value = { + type: "number", + required: true, + index: 2 + }; + + this.defaults.width = 960; + this.defaults.height = 500; + + //Properties + this.defaults.margin = [10, 40, 40, 40]; + this.defaults.title = null; + this.defaults.subtitle = null; + this.defaults.imagePath = null; + + this.defaults.clickMode = true; + this.defaults.hoverMode = true; + + //绘制属性 + this.defaults.lineSize = 2; + this.defaults.gridSize = 1; + this.defaults.nodeMode = true; + this.defaults.nodeRadius = 2; + this.defaults.hasXAxis = true; + this.defaults.hasYAxis = true; + this.defaults.xAxisTick = 10; + this.defaults.yAxisTick = 4; + this.defaults.xAxisOrient = "bottom"; + this.defaults.yAxisOrient = "left"; + this.defaults.xAxisPadding = 10; + this.defaults.yAxisPadding = 10; + this.defaults.xAxisFontSize = 10; + this.defaults.yAxisFontSize = 10; + this.defaults.xAxisStartDx = 20; + this.defaults.yAxisStartDy = 10; + this.defaults.xAxisEndDx = 10; + this.defaults.yAxisEndDy = 10; + + this.defaults.textLean = false; + + this.defaults.hasXGrid = true; + this.defaults.hasYGrid = true; + + //Color + this.defaults.backgroundColor = "#fff"; + this.defaults.titleColor = "#000"; + this.defaults.subtitleColor = "#000"; + this.defaults.AxisColor = "#000"; + this.defaults.gridColor = "#000"; + this.defaults.unchosen0pacity = 0.15; + this.defaults.grid0pacity = 0.1; + this.defaults.chosenGrid0pacity = 0.5; + + //设置用户指定属性 + this.setOptions(options); + + //创建画布 + this.createCanvas(); + } + }); + + /** + * 创建画布 + */ + Line.prototype.createCanvas = function () { + var conf = this.defaults; + + this.canvas = new Raphael(this.node, conf.width, conf.height); + + this.canvasF = this.node; + + var canvasStyle = this.canvasF.style; + canvasStyle.position = "relative"; + this.floatTag = DataV.FloatTag()(this.canvasF); + this.floatTag.css({ + "visibility": "hidden" + }); + }; + + /** + * 对原始数据进行处理 + */ + Line.prototype.setSource = function (source, map) { + map = this.map(map); + var dataTable; + if (DataV.detect(source) === 'Table_WITH_HEAD') { + dataTable = DataV.collectionify(source); + } else { + dataTable = source; + } + + var lines = _.groupBy(dataTable, map.line); + var linesData = []; + + var max, min; + var maxList = [], minList = []; + this.maxLength = Math.max.apply(null, _.map(lines, function (line) { + return line.length; + })); + + var titles; + _.forEach(lines, function (points, name) { + // initialize the nodes of line + var line = {name: name, id: name, data: [], tags: []}; + line.data = _.pluck(points, map.value); + titles = _.pluck(points, map.x); + linesData.push(line); + maxList[name] = Math.max.apply(null, line.data); + minList[name] = Math.min.apply(null, line.data); + }); + + var conf = this.defaults; + var margin = conf.margin; + this.xWidth = conf.width - margin[1] - margin[3]; + this.yHeight = conf.height - margin[0] - margin[2]; + + this.titles = titles; + this.linesData = linesData; + this.max = Math.max.apply(null, _.values(maxList)); + this.min = Math.min.apply(null, _.values(minList)); + this.maxList = maxList; + this.minList = minList; + + this.chosen = false; + this.chosenNum = -1; + }; + + /** + * 获取颜色函数 + * @return {function} DataV根据主题获取随机离散色函数 + */ + Line.prototype.getColor = function () { + var colorFunction = DataV.getDiscreteColor(); + return colorFunction; + }; + + /** + * 绘制X轴 + */ + Line.prototype.setXaxis = function (titles) { + var conf = this.defaults; + var canvas = this.canvas; + + if (conf.hasXAxis) { + var lineSize = conf.lineSize; + var gridSize = conf.gridSize; + var axisColor = conf.AxisColor; + var gridColor = conf.gridColor; + var grid0pacity = conf.grid0pacity; + var xAxisPadding = conf.xAxisPadding; + var margin = conf.margin; + var xAxisStartDx = conf.xAxisStartDx; + var xAxisEndDx = conf.xAxisEndDx; + var startX = margin[3] + xAxisStartDx; + var startY = conf.height - margin[2]; + var xWidth = this.xWidth; + var xDataArea = xWidth - xAxisStartDx - xAxisEndDx; + var maxLength = this.maxLength; + var yHeight = this.yHeight; + var fontSize = conf.xAxisFontSize; + var textLean = conf.textLean; + + var tickLength; + if (conf.hasXGrid) { + tickLength = yHeight; + } else { + tickLength = 5; + } + + var tick = conf.xAxisTick; + var tickStep; + if (tick % maxLength === 0) { + tickStep = xDataArea / (tick - 1); + } else { + tickStep = xDataArea / (this.maxLength - 1); + tick = this.maxLength; + } + + var xAxis = canvas.set(); + var xGrid = canvas.set(); + var xAxisText = canvas.set(); + xAxis.push(canvas.path("M" + (startX - xAxisStartDx) + "," + startY + "L" + (xWidth + margin[3]) + "," + startY)); + + var l = titles.length; + for (var i = 0; i < tick; i++) { + xGrid.push(canvas.path("M" + (startX + i * tickStep) + "," + startY + "L" + (startX + i * tickStep) + "," + (startY - tickLength))); + if (i < l) { + var thisText = canvas.text((startX + i * tickStep), startY + xAxisPadding, titles[i]); + if (textLean) { + var d = thisText.getBBox().width / 2; + var angle = 45 / 360 * Math.PI; + thisText.transform("r45t" + Math.cos(angle) * d + "," + Math.sin(angle) * d); + } + xAxisText.push(thisText); + } + } + + xAxis.attr({fill: axisColor, "stroke-width": lineSize}); + xGrid.attr({"stroke": gridColor, "stroke-width": gridSize, "stroke-opacity": grid0pacity}); + xAxisText.attr({"font-size": fontSize}); + this.xAxis = xAxis; + this.xGrid = xGrid; + this.xAxisText = xAxisText; + this.xTick = xDataArea / (maxLength - 1); + } + }; + + /** + * 绘制Y轴 + */ + Line.prototype.setYaxis = function () { + var conf = this.defaults; + var canvas = this.canvas; + + if (conf.hasYAxis) { + var lineSize = conf.lineSize; + var gridSize = conf.gridSize; + var axisColor = conf.AxisColor; + var gridColor = conf.gridColor; + var grid0pacity = conf.grid0pacity; + var margin = conf.margin; + var yAxisStartDy = conf.yAxisStartDy; + var yAxisEndDy = conf.yAxisEndDy; + var yHeight = this.yHeight; + var yDataArea = this.yHeight - yAxisStartDy - yAxisEndDy; + var xWidth = this.xWidth; + var startX = margin[3]; + var startY = margin[0] + yHeight - yAxisStartDy; + var yAxisPadding = conf.yAxisPadding; + var max = this.max; + var min = this.min; + var fontSize = conf.yAxisFontSize; + + var tick = conf.yAxisTick; + var tickStep = yDataArea / (tick - 1); + + var d = (max - min) % (tick - 1); + if (d !== 0) { + max = max + (tick - 1) - d; + } + d = (max - min) / (tick - 1); + + var tickLength; + if (conf.hasYGrid) { + tickLength = xWidth; + } else { + tickLength = 5; + } + + var yAxis = canvas.set(); + var yGrid = canvas.set(); + var yAxisText = canvas.set(); + yAxis.push(canvas.path("M" + startX + "," + (startY + yAxisStartDy) + "L" + startX + "," + margin[0])); + + for (var i = 0; i < tick; i++) { + yGrid.push(canvas.path("M" + startX + "," + (startY - i * tickStep) + "L" + (startX + tickLength) + "," + (startY - i * tickStep))); + yAxisText.push(canvas.text(startX - yAxisPadding, (startY - i * tickStep), (min + i * d))); + } + + yAxis.attr({fill: axisColor, "stroke-width": lineSize}); + yGrid.attr({"stroke": gridColor, "stroke-width": gridSize, "stroke-opacity": grid0pacity}); + yAxisText.attr({"font-size": fontSize}); + this.yAxis = yAxis; + this.yGrid = yGrid; + this.yAxisText = yAxisText; + this.yDataArea = yDataArea; + + this.yMatchNum = tickStep / d; + this.y0 = startY; + } + }; + + /** + * 绘制背景 + */ + Line.prototype.setBackground = function () { + var conf = this.defaults; + var canvas = this.canvas; + var backgroundColor = conf.backgroundColor; + var imagePath = conf.imagePath; + var xWidth = this.xWidth; + var yHeight = this.yHeight; + var yAxisStartDy = conf.yAxisStartDy; + var x0 = conf.margin[3]; + var y0 = this.y0 + yAxisStartDy; + + var rect; + if (imagePath !== null) { + rect = canvas.image(imagePath, x0, (y0 - yHeight), xWidth, yHeight); + } else { + rect = canvas.rect(x0, (y0 - yHeight), xWidth, yHeight).attr({"fill": backgroundColor,"fill-opacity": 0, "stroke": "none"}); + } + + rect.toBack(); + this.background = rect; + }; + + /** + * 渲染折线图 + * Examples: + * ``` + * var line = new Line("chart"); + * line.setSource(source); + * line.render(); + * ``` + * @param {object} options options json object for determin line style. + */ + Line.prototype.render = function () { + this.canvas.clear(); + this.createCanvas(); + var conf = this.defaults; + var linesData = this.linesData; + var nodesList = []; + + this.setXaxis(this.titles); + this.setYaxis(); + + this.setBackground(); + + var canvas = this.canvas; + var nodeMode = conf.nodeMode; + var getColor = this.getColor(); + var lineSize = conf.lineSize; + var min = this.min; + var xAxisStartDx = conf.xAxisStartDx; + var xTick = this.xTick; + var yMatchNum = this.yMatchNum; + var x0 = conf.margin[3] + xAxisStartDx; + var y0 = this.y0; + var radius = conf.nodeRadius; + + var lines = canvas.set(); + linesData.forEach(function (d, i) { + var nodeData = d.data; + var nodes = canvas.set(); + var color = getColor(i); + var linePath = "M"; + + for (var j = 0, l = nodeData.length; j < l; j++) { + var x = x0 + xTick * (j); + var y = y0 - ((nodeData[j] - min) * yMatchNum); + + linePath = linePath + x + "," + y; + + if (j < l - 1) { + linePath = linePath + "L"; + } + + if (nodeMode) { + var thisNode = canvas.circle(x, y, radius * 2).attr({fill: color, "stroke": "none"}); + thisNode.data('num', j); + thisNode.data('lineNum', i); + thisNode.data('data', nodeData[j]); + nodes.push(thisNode); + } + } + + lines.push(canvas.path(linePath).attr({"stroke": color, "stroke-width": lineSize}).data('num', i)); + if (nodeMode) { + nodesList.push(nodes.toFront()); + } + }); + + this.lines = lines; + this.nodesList = nodesList; + + this.interactive(); + }; + + /** + * 添加交互选项 + */ + Line.prototype.interactive = function () { + var that = this; + var conf = this.defaults; + var hoverMode = conf.hoverMode; + var clickMode = conf.clickMode; + var nodeMode = conf.nodeMode; + var chosen = this.chosen; + var chosenNum = this.chosenNum; + var lines = this.lines; + var uo = conf.unchosen0pacity; + var nodesList = this.nodesList; + var xGrid = this.xGrid; + var grid0pacity = conf.grid0pacity; + var cgo = conf.chosenGrid0pacity; + var xAxisText = this.xAxisText; + var xAxisFontSize = conf.xAxisFontSize; + + var highLight = function (num) { + var line = lines[num]; + lines.attr({"stroke-opacity": uo}); + + nodesList.forEach(function (d) { + d.attr({"fill-opacity": uo}); + }); + + line.attr({"stroke-opacity": 1}).toFront(); + nodesList[num].attr({"fill-opacity": 1}).toFront(); + }; + + var unhighLinght = function () { + lines.forEach(function (d) { + d.attr({"stroke-opacity": 1}); + }); + nodesList.forEach(function (d) { + d.attr({"fill-opacity": 1}); + }); + }; + + var background = this.background; + + if (clickMode){ + background.click(function () { + if (chosen) { + unhighLinght(); + chosen = false; + that.chosen = chosen; + } + }); + } + + var floatTag = this.floatTag; + $(this.node).append(this.floatTag); + // if (hoverMode) { + // background.mouseover(function () { + + // }).mouseout(function () { + // floatTag.css({"visibility" : "hidden"}); + // }); + // } + + lines.forEach(function (d) { + if (hoverMode) { + d.mouseover(function () { + if (!chosen) { + highLight(d.data('num')); + } + }).mouseout(function () { + if (!chosen) { + unhighLinght(); + } + }); + } + + if (clickMode){ + d.click(function () { + chosenNum = d.data('num'); + highLight(chosenNum); + + chosen = true; + that.chosen = chosen; + }); + } + }); + + if (nodeMode){ + var radius = conf.nodeRadius; + + nodesList.forEach(function (d) { + d.forEach (function (d) { + if (hoverMode) { + var nodeNum = d.data('num'); + d.mouseover(function () { + d.animate({r: (radius + 2) * 2}, 100); + xGrid[nodeNum].animate({'stroke-opacity': cgo}, 100); + xAxisText[nodeNum].animate({'font-size': xAxisFontSize * 2}, 100); + floatTag.html('
' + d.data('data') + '
'); + floatTag.css({"visibility": "visible"}); + if (!chosen) { + highLight(d.data("lineNum")); + nodesList.forEach(function (d) { + if (nodeNum < d.length) { + d[nodeNum].attr({"fill-opacity": 1}); + } + }); + } + }).mouseout(function () { + d.animate({r: radius * 2}, 100); + xGrid[nodeNum].animate({'stroke-opacity': grid0pacity}, 100); + xAxisText[nodeNum].animate({'font-size': xAxisFontSize}, 100); + floatTag.css({"visibility": "hidden"}); + if (!chosen) { + unhighLinght(); + } + }); + } + + if (clickMode){ + d.click(function () { + chosenNum = d.data('lineNum'); + highLight(chosenNum); + + chosen = true; + that.chosen = chosen; + }); + } + }); + }); + } + }; + + return Line; }); \ No newline at end of file diff --git a/lib/charts/matrix.js b/lib/charts/matrix.js index 3e5fe49..8438c13 100644 --- a/lib/charts/matrix.js +++ b/lib/charts/matrix.js @@ -1,434 +1,434 @@ -/*global EventProxy, d3, Raphael, $ */ -/*! - * Matrix的兼容定义 - */ -;(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];}); - } -})('Matrix', function (require) { - var DataV = require('DataV'); - var theme = DataV.Themes; - - /** - * 构造函数 - */ - var Matrix = DataV.extend(DataV.Chart, { - initialize: function (node, options) { - this.type = "Matrix"; - this.node = this.checkContainer(node); - - // Properties - this.font = {}; - - // Canvas - this.defaults.width = 1200; - this.defaults.height = 1200; - this.defaults.axisWidth = 40; - - this.setOptions(options); - this.createCanvas(); - this.move = false; - } - }); - - Matrix.prototype.getDataTable = function (table) { - var title = table[0]; - table = table.slice(1); - - var titleLength = title.length; - var tableWidth = table[0].length; - var tableHeight = table.length; - - this.tableWidth = tableWidth; - this.tableHeight = tableHeight; - - //for symmetric matrix - if (tableWidth !== title.length || tableHeight !== title.length) { - throw new Error("This matrix is not symmetric matrix!!!"); - } else { - this.tableWidth = tableWidth; - this.tableHeight = tableHeight; - } - - this.title = title; - return table; - }; - - Matrix.prototype.setSource = function (source) { - var conf = this.defaults; - - this.source = this.getDataTable(source); - this.hasSort = false; - // this.source = this.remapSource(source); - }; - - Matrix.prototype.layout = function () { - var conf = this.defaults; - var width = conf.width; - var height = conf.height; - var tableWidth = this.tableWidth; - var tableHeight = this.tableHeight; - var axisWidth = conf.axisWidth; - - this.cellWidth = Math.min((width - axisWidth) / tableWidth, (height - axisWidth) / tableHeight); - - var startX; - var startY; - var bRectWidth; - var matrixWidth; - - if (width > height) { - startX = (width - height)/2 + axisWidth; - startY = axisWidth; - bRectWidth = height - axisWidth; - matrixWidth = bRectWidth - axisWidth; - } else if (height > width) { - startX = axisWidth; - startY = (height - width) + axisWidth; - bRectWidth = width - axisWidth; - } else { - startX = axisWidth; - startY = axisWidth; - bRectWidth = width - axisWidth; - matrixWidth = bRectWidth - axisWidth; - } - - this.startX = startX; - this.startY = startY; - this.bRectWidth = bRectWidth; - this.matrixWidth = matrixWidth; - }; - - Matrix.prototype.getColor = function (i) { - var colorMatrix = DataV.getColor(); - var length = colorMatrix.length; - var num = i % length; - //var color = '#939598'; - var color = '#FFFFFF'; - - if (num !== 0) { - color = colorMatrix[num][0]; - } - - return color; - }; - - Matrix.prototype.createCanvas = function () { - var conf = this.defaults; - this.canvas = new Raphael(this.node, conf.width, conf.height); - - this.DOMNode = $(this.canvas.canvas); - var that = this; - this.DOMNode.click(function (event) { - that.trigger("click", event); - that.update(); - }); - this.DOMNode.dblclick(function (event) { - that.trigger("dblclick", event); - }); - - var mousewheel = document.all ? "mousewheel" : "DOMMouseScroll"; - this.DOMNode.bind(mousewheel, function (event) { - that.trigger("mousewheel", event); - }); - - this.DOMNode.bind("contextmenu", function (event) { - that.trigger("contextmenu", event); - }); - - this.DOMNode.delegate("circle", "click", function (event) { - that.trigger("circle_click", event); - }); - - this.DOMNode.delegate("circle", "mouseover", function (event) { - that.trigger("circle_mouseover", event); - }); - - this.DOMNode.delegate("circle", "mouseout", function (event) { - that.trigger("circle_mouseout", event); - }); - }; - - Matrix.prototype.generatePaths = function () { - var canvas = this.canvas; - var source = this.source; - var conf = this.defaults; - var width = conf.width; - var height = conf.height; - var startX = this.startX; - var startY = this.startY; - var cellWidth = this.cellWidth; - var tableWidth = this.tableWidth; - var tableHeight = this.tableHeight; - var bRectWidth = this.bRectWidth; - var matrixWidth = this.matrixWidth; - - //canvas.clear(); - // var color = this.getColor(); - // var font = this.getFont(); - var font_family = '微软雅黑'; - var font_size = 8; - - var title = this.title; - - var row = []; - var columnLine = []; - var columnText = []; - - var backgroundRect = canvas.rect(startX, startY, bRectWidth, bRectWidth); - //backgroundRect.attr({fill: "#939598", stroke: "none", "fill-opacity": 0.8}); - backgroundRect.attr({fill: "#ffffff", stroke: "none", "fill-opacity": 0.8}); - backgroundRect.toBack(); - - var sort; - if (this.hasSort) { - sort = this.sort; - } - var i, j, a, b, color, rect; - var rects = []; //for column change move rect - for (i = 0; i < tableHeight; i++) { - if (!this.hasSort){ - a = i; - } else { - for (j = 0; j < sort.length; j++) { - if (sort[j] === i) { - a = j; - } - } - } - var rowRect = canvas.set(); - canvas.path("M" + startX + " " + (startY + cellWidth * i) + "L" + (startX + matrixWidth + 10 + cellWidth) + " " - + (startY + cellWidth * i)).attr({stroke: "#D1D1D1", "stroke-width": 1}); - rowRect.push(canvas.text(-20, cellWidth / 2, title[i]) - .attr({"fill": "#000000", - "fill-opacity": 0.7, - "font-family": "Verdana", - //"font-weight": "bold", - "font-size": 12})); - - for (j = 0; j < tableWidth; j++) { - if (!this.hasSort) { - color = this.getColor(source[i][j]); - } else { - color = this.getColor(source[i][sort[j]]); - } - rect = canvas.rect(cellWidth * j, 0, cellWidth, cellWidth) - .attr({stroke: "none", fill: color, "fill-opacity": 0.8}); - rowRect.push(rect); - rects.push(rect); - } - - rowRect.transform("t" + startX + ", " + (startY + cellWidth * a)); - row.push(rowRect); - } - - canvas.path("M" + startX + " " + (startY + cellWidth * tableHeight) + "L" + (startX + matrixWidth + 10 + cellWidth) + " " - + (startY + cellWidth * tableHeight)).attr({stroke: "#D1D1D1", "stroke-width": 1}); - - for (i = 0; i < tableWidth; i++) { - // var columnLine = canvas.set(); - // var columnText = canvas.set(); - if (!this.hasSort){ - a = i; - } else { - for (j = 0; j < sort.length; j++) { - if (sort[j] === i) { - a = j; - } - } - } - columnLine.push(canvas.path("M0 0L0 " + matrixWidth + 10 + cellWidth) - .attr({stroke: "#D1D1D1", "stroke-width": 1}) - .transform("t" + (startX + cellWidth * a) + ", " + startY)); - columnText.push(canvas.text(cellWidth / 2, -20, title[i]) - .attr({"fill": "#000000", - "fill-opacity": 0.7, - "font-family": "Verdana", - //"font-weight": "bold", - "font-size": 12}) - .transform("t" + (startX + cellWidth * a) + ", " + startY + "r90")); - } - - columnLine.push(canvas.path("M0 0L0 " + matrixWidth + 10 + cellWidth) - .attr({stroke: "#D1D1D1", "stroke-width": 1}) - .transform("t" + (startX + cellWidth * tableWidth) + ", " + startY)); - - this.row = row; - this.columnText = columnText; - this.columnLine = columnLine; - this.rects = rects; - }; - - Matrix.prototype.getSort = function (source) { - var sumQueue = []; - var sort = []; - var rowData; - var rowLength; - var sum; - var means; - var matrixD = []; - var quareSum; - var rowquareSum = []; - - var i, j, k; - for (i = 0 ; i < source.length ; i++) { - rowData = source[i]; - rowLength = rowData.length; - sum = 0; - quareSum = 0; - - for (j = 0 ; j < rowLength ; j++) { - sum = sum + rowData[j]; - } - - means = sum / rowLength; - for (j = 0 ; j < rowLength ; j++) { - rowData[j] = rowData[j] - means; - quareSum = quareSum + Math.pow(rowData[j], 2); - } - - quareSum = Math.sqrt(quareSum); - - rowquareSum.push(quareSum); - matrixD.push(rowData); - } - - var rowI; - var rowJ; - var matrixR = []; - - for (i = 0 ; i < source.length ; i++) { - matrixR[i] = []; - for (j = 0 ; j < source.length ; j++) { - matrixR[i][j] = 0; - } - } - - for (i = 0 ; i < source.length ; i++) { - rowI = matrixD[i]; - matrixR[i][i] = source[i][i]; - for (j = i + 1 ; j < source.length ; j++) { - sum = 0; - rowJ = matrixD[j]; - for (k = 0; k < rowLength; k++) { - sum = sum + rowI[k] * rowJ[k]; - } - - sum = sum / (rowquareSum[i] * rowquareSum[j]); - matrixR[i][j] = sum; - matrixR[j][i] = sum; - } - } - - - - return matrixR; - }; - - Matrix.prototype.update = function () { - var i, j; - var source = []; - for(i = 0; i < this.source.length ; i++){ - source[i] = this.source[i].concat(); - } - - var sort = []; - for (i = 0; i < source[0].length; i++) { - sort.push(i); - } - - if (this.hasSort) { - this.sort = sort; - this.hasSort = false; - } else { - var getSort = this.getSort; - var i, j; - var pt; - var nowSort = []; - var iterations = 12; - - for (i = 0; i < iterations; i++) { - source = getSort(source); - } - - nowSort = source[0]; - - var a, b; - for (i = 1; i < sort.length; i++) { - a = sort[i]; - for (j = i + 1; j < sort.length; j++) { - b = sort[j]; - if (nowSort[a] < nowSort[b]) { - pt = sort[i]; - sort[i] = sort[j]; - sort[j] = pt; - } - } - } - sort = [0,7,5,2,8,3,1,9,6,14,15,4,13,10,16,11,12]; - this.sort = sort; - this.hasSort = true; - } - - if (!this.move) { - this.move = true; - var rects = this.rects; - var num; - var startX = this.startX; - var startY = this.startY; - var cellWidth = this.cellWidth; - - var rowAnim; - var columnLineAnim; - var columnTextAnim; - var anim; - - for (i = 0; i < sort.length; i++) { - num = sort[i]; - // if (num != i) { - rowAnim = Raphael.animation({transform: ["t", startX, (startY + cellWidth * i)]}, 200, "<>"); - this.row[num].animate(rowAnim.delay(100 * i)); - // } - } - - var that = this; - var moveEnd = function () { - that.move = false; - }; - - for (i = 0; i < sort.length; i++) { - num = sort[i]; - // if (num != i) { - //columnLineAnim = Raphael.animation({transform: ["t", (startX + cellWidth * i), startY]}, 1000, "<>"); - columnTextAnim = Raphael.animation({transform: ["t", (startX + cellWidth * i), startY, "r", 90]}, - 200, "<>"); - //this.columnLine[num].animate(columnLineAnim.delay(500 * (i + sort.length + 1))); - this.columnText[num].animate(columnTextAnim.delay(100 * (i + sort.length + 1))); - - for (j = 0; j < sort.length; j++) { - if (i === sort.length - 1 && j === sort.length - 1) { - anim = Raphael.animation({'x': cellWidth * i}, 200, "<>", moveEnd); - } else { - anim = Raphael.animation({'x': cellWidth * i}, 200, "<>"); - } - rects[j * sort.length + num].animate(anim.delay(100 * (i + sort.length + 1))); - } - // } - } - } - }; - - Matrix.prototype.render = function (options) { - if (!this.move) { - this.canvas.clear(); - this.setOptions(options); - this.layout(); - this.generatePaths(); - } - }; - - return Matrix; +/*global EventProxy, d3, Raphael, $ */ +/*! + * Matrix的兼容定义 + */ +;(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];}); + } +})('Matrix', function (require) { + var DataV = require('DataV'); + var theme = DataV.Themes; + + /** + * 构造函数 + */ + var Matrix = DataV.extend(DataV.Chart, { + initialize: function (node, options) { + this.type = "Matrix"; + this.node = this.checkContainer(node); + + // Properties + this.font = {}; + + // Canvas + this.defaults.width = 1200; + this.defaults.height = 1200; + this.defaults.axisWidth = 40; + + this.setOptions(options); + this.createCanvas(); + this.move = false; + } + }); + + Matrix.prototype.getDataTable = function (table) { + var title = table[0]; + table = table.slice(1); + + var titleLength = title.length; + var tableWidth = table[0].length; + var tableHeight = table.length; + + this.tableWidth = tableWidth; + this.tableHeight = tableHeight; + + //for symmetric matrix + if (tableWidth !== title.length || tableHeight !== title.length) { + throw new Error("This matrix is not symmetric matrix!!!"); + } else { + this.tableWidth = tableWidth; + this.tableHeight = tableHeight; + } + + this.title = title; + return table; + }; + + Matrix.prototype.setSource = function (source) { + var conf = this.defaults; + + this.source = this.getDataTable(source); + this.hasSort = false; + // this.source = this.remapSource(source); + }; + + Matrix.prototype.layout = function () { + var conf = this.defaults; + var width = conf.width; + var height = conf.height; + var tableWidth = this.tableWidth; + var tableHeight = this.tableHeight; + var axisWidth = conf.axisWidth; + + this.cellWidth = Math.min((width - axisWidth) / tableWidth, (height - axisWidth) / tableHeight); + + var startX; + var startY; + var bRectWidth; + var matrixWidth; + + if (width > height) { + startX = (width - height)/2 + axisWidth; + startY = axisWidth; + bRectWidth = height - axisWidth; + matrixWidth = bRectWidth - axisWidth; + } else if (height > width) { + startX = axisWidth; + startY = (height - width) + axisWidth; + bRectWidth = width - axisWidth; + } else { + startX = axisWidth; + startY = axisWidth; + bRectWidth = width - axisWidth; + matrixWidth = bRectWidth - axisWidth; + } + + this.startX = startX; + this.startY = startY; + this.bRectWidth = bRectWidth; + this.matrixWidth = matrixWidth; + }; + + Matrix.prototype.getColor = function (i) { + var colorMatrix = DataV.getColor(); + var length = colorMatrix.length; + var num = i % length; + //var color = '#939598'; + var color = '#FFFFFF'; + + if (num !== 0) { + color = colorMatrix[num][0]; + } + + return color; + }; + + Matrix.prototype.createCanvas = function () { + var conf = this.defaults; + this.canvas = new Raphael(this.node, conf.width, conf.height); + + this.DOMNode = $(this.canvas.canvas); + var that = this; + this.DOMNode.click(function (event) { + that.trigger("click", event); + that.update(); + }); + this.DOMNode.dblclick(function (event) { + that.trigger("dblclick", event); + }); + + var mousewheel = document.all ? "mousewheel" : "DOMMouseScroll"; + this.DOMNode.bind(mousewheel, function (event) { + that.trigger("mousewheel", event); + }); + + this.DOMNode.bind("contextmenu", function (event) { + that.trigger("contextmenu", event); + }); + + this.DOMNode.delegate("circle", "click", function (event) { + that.trigger("circle_click", event); + }); + + this.DOMNode.delegate("circle", "mouseover", function (event) { + that.trigger("circle_mouseover", event); + }); + + this.DOMNode.delegate("circle", "mouseout", function (event) { + that.trigger("circle_mouseout", event); + }); + }; + + Matrix.prototype.generatePaths = function () { + var canvas = this.canvas; + var source = this.source; + var conf = this.defaults; + var width = conf.width; + var height = conf.height; + var startX = this.startX; + var startY = this.startY; + var cellWidth = this.cellWidth; + var tableWidth = this.tableWidth; + var tableHeight = this.tableHeight; + var bRectWidth = this.bRectWidth; + var matrixWidth = this.matrixWidth; + + //canvas.clear(); + // var color = this.getColor(); + // var font = this.getFont(); + var font_family = '微软雅黑'; + var font_size = 8; + + var title = this.title; + + var row = []; + var columnLine = []; + var columnText = []; + + var backgroundRect = canvas.rect(startX, startY, bRectWidth, bRectWidth); + //backgroundRect.attr({fill: "#939598", stroke: "none", "fill-opacity": 0.8}); + backgroundRect.attr({fill: "#ffffff", stroke: "none", "fill-opacity": 0.8}); + backgroundRect.toBack(); + + var sort; + if (this.hasSort) { + sort = this.sort; + } + var i, j, a, b, color, rect; + var rects = []; //for column change move rect + for (i = 0; i < tableHeight; i++) { + if (!this.hasSort){ + a = i; + } else { + for (j = 0; j < sort.length; j++) { + if (sort[j] === i) { + a = j; + } + } + } + var rowRect = canvas.set(); + canvas.path("M" + startX + " " + (startY + cellWidth * i) + "L" + (startX + matrixWidth + 10 + cellWidth) + " " + + (startY + cellWidth * i)).attr({stroke: "#D1D1D1", "stroke-width": 1}); + rowRect.push(canvas.text(-20, cellWidth / 2, title[i]) + .attr({"fill": "#000000", + "fill-opacity": 0.7, + "font-family": "Verdana", + //"font-weight": "bold", + "font-size": 12})); + + for (j = 0; j < tableWidth; j++) { + if (!this.hasSort) { + color = this.getColor(source[i][j]); + } else { + color = this.getColor(source[i][sort[j]]); + } + rect = canvas.rect(cellWidth * j, 0, cellWidth, cellWidth) + .attr({stroke: "none", fill: color, "fill-opacity": 0.8}); + rowRect.push(rect); + rects.push(rect); + } + + rowRect.transform("t" + startX + ", " + (startY + cellWidth * a)); + row.push(rowRect); + } + + canvas.path("M" + startX + " " + (startY + cellWidth * tableHeight) + "L" + (startX + matrixWidth + 10 + cellWidth) + " " + + (startY + cellWidth * tableHeight)).attr({stroke: "#D1D1D1", "stroke-width": 1}); + + for (i = 0; i < tableWidth; i++) { + // var columnLine = canvas.set(); + // var columnText = canvas.set(); + if (!this.hasSort){ + a = i; + } else { + for (j = 0; j < sort.length; j++) { + if (sort[j] === i) { + a = j; + } + } + } + columnLine.push(canvas.path("M0 0L0 " + matrixWidth + 10 + cellWidth) + .attr({stroke: "#D1D1D1", "stroke-width": 1}) + .transform("t" + (startX + cellWidth * a) + ", " + startY)); + columnText.push(canvas.text(cellWidth / 2, -20, title[i]) + .attr({"fill": "#000000", + "fill-opacity": 0.7, + "font-family": "Verdana", + //"font-weight": "bold", + "font-size": 12}) + .transform("t" + (startX + cellWidth * a) + ", " + startY + "r90")); + } + + columnLine.push(canvas.path("M0 0L0 " + matrixWidth + 10 + cellWidth) + .attr({stroke: "#D1D1D1", "stroke-width": 1}) + .transform("t" + (startX + cellWidth * tableWidth) + ", " + startY)); + + this.row = row; + this.columnText = columnText; + this.columnLine = columnLine; + this.rects = rects; + }; + + Matrix.prototype.getSort = function (source) { + var sumQueue = []; + var sort = []; + var rowData; + var rowLength; + var sum; + var means; + var matrixD = []; + var quareSum; + var rowquareSum = []; + + var i, j, k; + for (i = 0 ; i < source.length ; i++) { + rowData = source[i]; + rowLength = rowData.length; + sum = 0; + quareSum = 0; + + for (j = 0 ; j < rowLength ; j++) { + sum = sum + rowData[j]; + } + + means = sum / rowLength; + for (j = 0 ; j < rowLength ; j++) { + rowData[j] = rowData[j] - means; + quareSum = quareSum + Math.pow(rowData[j], 2); + } + + quareSum = Math.sqrt(quareSum); + + rowquareSum.push(quareSum); + matrixD.push(rowData); + } + + var rowI; + var rowJ; + var matrixR = []; + + for (i = 0 ; i < source.length ; i++) { + matrixR[i] = []; + for (j = 0 ; j < source.length ; j++) { + matrixR[i][j] = 0; + } + } + + for (i = 0 ; i < source.length ; i++) { + rowI = matrixD[i]; + matrixR[i][i] = source[i][i]; + for (j = i + 1 ; j < source.length ; j++) { + sum = 0; + rowJ = matrixD[j]; + for (k = 0; k < rowLength; k++) { + sum = sum + rowI[k] * rowJ[k]; + } + + sum = sum / (rowquareSum[i] * rowquareSum[j]); + matrixR[i][j] = sum; + matrixR[j][i] = sum; + } + } + + + + return matrixR; + }; + + Matrix.prototype.update = function () { + var i, j; + var source = []; + for(i = 0; i < this.source.length ; i++){ + source[i] = this.source[i].concat(); + } + + var sort = []; + for (i = 0; i < source[0].length; i++) { + sort.push(i); + } + + if (this.hasSort) { + this.sort = sort; + this.hasSort = false; + } else { + var getSort = this.getSort; + var i, j; + var pt; + var nowSort = []; + var iterations = 12; + + for (i = 0; i < iterations; i++) { + source = getSort(source); + } + + nowSort = source[0]; + + var a, b; + for (i = 1; i < sort.length; i++) { + a = sort[i]; + for (j = i + 1; j < sort.length; j++) { + b = sort[j]; + if (nowSort[a] < nowSort[b]) { + pt = sort[i]; + sort[i] = sort[j]; + sort[j] = pt; + } + } + } + sort = [0,7,5,2,8,3,1,9,6,14,15,4,13,10,16,11,12]; + this.sort = sort; + this.hasSort = true; + } + + if (!this.move) { + this.move = true; + var rects = this.rects; + var num; + var startX = this.startX; + var startY = this.startY; + var cellWidth = this.cellWidth; + + var rowAnim; + var columnLineAnim; + var columnTextAnim; + var anim; + + for (i = 0; i < sort.length; i++) { + num = sort[i]; + // if (num != i) { + rowAnim = Raphael.animation({transform: ["t", startX, (startY + cellWidth * i)]}, 200, "<>"); + this.row[num].animate(rowAnim.delay(100 * i)); + // } + } + + var that = this; + var moveEnd = function () { + that.move = false; + }; + + for (i = 0; i < sort.length; i++) { + num = sort[i]; + // if (num != i) { + //columnLineAnim = Raphael.animation({transform: ["t", (startX + cellWidth * i), startY]}, 1000, "<>"); + columnTextAnim = Raphael.animation({transform: ["t", (startX + cellWidth * i), startY, "r", 90]}, + 200, "<>"); + //this.columnLine[num].animate(columnLineAnim.delay(500 * (i + sort.length + 1))); + this.columnText[num].animate(columnTextAnim.delay(100 * (i + sort.length + 1))); + + for (j = 0; j < sort.length; j++) { + if (i === sort.length - 1 && j === sort.length - 1) { + anim = Raphael.animation({'x': cellWidth * i}, 200, "<>", moveEnd); + } else { + anim = Raphael.animation({'x': cellWidth * i}, 200, "<>"); + } + rects[j * sort.length + num].animate(anim.delay(100 * (i + sort.length + 1))); + } + // } + } + } + }; + + Matrix.prototype.render = function (options) { + if (!this.move) { + this.canvas.clear(); + this.setOptions(options); + this.layout(); + this.generatePaths(); + } + }; + + return Matrix; }); \ No newline at end of file diff --git a/lib/charts/parallel.js b/lib/charts/parallel.js index 3b9333f..83e26da 100644 --- a/lib/charts/parallel.js +++ b/lib/charts/parallel.js @@ -1,591 +1,591 @@ -/*global Raphael, d3 */ -/*! - * Parallel的兼容性定义 - */ -;(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];}); - } -})('Parallel', function (require) { - var DataV = require('DataV'); - var Axis = require('Axis'); - var Brush = require('Brush'); - - /** - * 构造函数 - * Options: - * - * - `width` 数字,图片宽度,默认为750,表示图片高750px - * - `height` 数字,图片高度,默认为500 - * - `marginWidth` 数组,表示图片上、右、下、左的边距,默认为 [20, 20, 20, 20] - * - `backgroundAttr` 对象,没有选中的线的样式,默认为{"fill": "none", "stroke": "#ccc", "stroke-opacity": 0.4}, 具体设置方式请参考Raphael手册:http://raphaeljs.com/reference.html#Element.attr - * - `foregroundAttr` 对象,被选中的线的样式,默认为{"fill": "none", "stroke": "steelblue", "stroke-opacity": 0.7}, 具体设置方式请参考Raphael手册:http://raphaeljs.com/reference.html#Element.attr - * - `axisStyle` 对象,设置坐标轴属性。3中坐标轴属性:domainAttr表示坐标轴线属性。tickAttr表示坐标轴标尺属性。tickTextAttr表示坐标轴文字属性。具体设置方式请参考Raphael手册:http://raphaeljs.com/reference.html#Element.attr - * - `customEvent` 函数对象,其中有3个自定义函数。`brushstart` 函数,表示刚开始拖选区间的事件响应,默认为空函数; `brushend` 函数,表示拖选结束后的事件响应,默认为空函数; `brush` 函数,表示拖选时的事件响应,默认为空函数; 这些函数可以在创建对象或setOption()时一起设置,也可以通过on()函数单独设置。 - * - * @param {Node|String|jQuery} node 容器节点,文档节点、ID或者通过jQuery查询出来的对象 - */ - var Parallel = DataV.extend(DataV.Chart, { - initialize: function (node, options) { - this.type = "Parallel"; - this.node = this.checkContainer(node); - - // Properties - this.allDimensions = []; - this.dimensions = []; - this.dimensionType = {}; - this.dimensionDomain = {}; - this.dimensionExtent = {}; - - // Canvas - this.defaults.width = 750; - this.defaults.height = 500; - this.defaults.marginWidth = [20, 20, 20, 20]; - this.defaults.backgroundAttr = {"fill": "none", "stroke": "#ccc", "stroke-opacity": 0.4}; - this.defaults.foregroundAttr = {"fill": "none", "stroke": "steelblue", "stroke-opacity": 0.7}; - - this.defaults.axisStyle = { - domainAttr : {"stroke": "#000"},//坐标轴线 - tickAttr : {"stroke": "#000"},//坐标轴标尺 - tickTextAttr : {}//坐标轴文字 - } - - this.defaults.customEvent = { - "brushstart": function () {}, - "brushend": function () {}, - "brush": function () {} - }; - - this.setOptions(options); - this.createCanvas(); - } - }); - - /** - * choose dimension - * @param {array} dimen Array of column names - */ - Parallel.prototype.chooseDimensions = function (dimen) { - var conf = this.defaults; - this.dimensions = []; - for (var i = 0, l = dimen.length; i end) { - temp = start; - start = end; - end = temp; - } - start = Math.max(0, start + 0.5 - 0.5); - end = Math.min(this.dimensionDomain[dimen].length, end + 0.5 + 0.5); - return [start, end]; - }; - - /*! - * create canvas - */ - Parallel.prototype.createCanvas = function () { - var conf = this.defaults; - this.node.style.position = "relative"; - this.canvas = Raphael(this.node, conf.width, conf.height); - - //console.log(this.canvas); - }; - - /*! - * get color - */ - Parallel.prototype.getColor = function (colorJson) { - var colorM = DataV.getColor(); - var color; - var colorStyle = colorJson ? colorJson : {}; - var colorMode = colorStyle.mode ? colorStyle.mode : 'default'; - switch (colorMode){ - case "gradient": - var index = colorJson.index ? colorJson.index : 0; - index = index <0 ? 0 : (index>colorM.length-1 ? colorM.length-1 : index); - color = d3.interpolateRgb.apply(null, [colorM[index][0],colorM[index][1]]); - break; - case "random": - case "default": - var ratio = colorStyle.ratio ? colorStyle.ratio : 0; - if(ratio <0 ){ratio=0;} - if(ratio > 1) { ratio =1;} - var colorArray =[]; - for (var i=0, l=colorM.length; i end) { + temp = start; + start = end; + end = temp; + } + start = Math.max(0, start + 0.5 - 0.5); + end = Math.min(this.dimensionDomain[dimen].length, end + 0.5 + 0.5); + return [start, end]; + }; + + /*! + * create canvas + */ + Parallel.prototype.createCanvas = function () { + var conf = this.defaults; + this.node.style.position = "relative"; + this.canvas = Raphael(this.node, conf.width, conf.height); + + //console.log(this.canvas); + }; + + /*! + * get color + */ + Parallel.prototype.getColor = function (colorJson) { + var colorM = DataV.getColor(); + var color; + var colorStyle = colorJson ? colorJson : {}; + var colorMode = colorStyle.mode ? colorStyle.mode : 'default'; + switch (colorMode){ + case "gradient": + var index = colorJson.index ? colorJson.index : 0; + index = index <0 ? 0 : (index>colorM.length-1 ? colorM.length-1 : index); + color = d3.interpolateRgb.apply(null, [colorM[index][0],colorM[index][1]]); + break; + case "random": + case "default": + var ratio = colorStyle.ratio ? colorStyle.ratio : 0; + if(ratio <0 ){ratio=0;} + if(ratio > 1) { ratio =1;} + var colorArray =[]; + for (var i=0, l=colorM.length; i2: small change; - - this.setOptions(options); - this.createCanvas(); - } - }); - - /** - * Stream图纬度描述 - */ - Stream.dimension = {}; - /** - * 流向纬度,例如,时间 - */ - Stream.dimension.stream = { - type: "string", - required: true - }; - /** - * 堆叠纬度,例如,按类目 - */ - Stream.dimension.stack = { - type: "string", - required: true - }; - /** - * 值纬度,在流向和堆叠纬度上的值 - */ - Stream.dimension.value = { - type: "number", - required: true - }; - - - Stream.prototype.initLegend = function () { - var conf = this.defaults; - this.legend = $(create("div")).css({ - "overflow": "hidden", - "width": conf.legendWidth - conf.legendIndent, - "padding": "10px 0 10px 0" - }); - this.leftContainer = $(create("div")).css({ - "width": conf.legendWidth - 4, - "overflow-x": "hidden" - }).append(this.legend); - }; - - Stream.prototype.initNav = function () { - var conf = this.defaults; - this.navi = $(create("div")).css({ - "borderTop": "1px solid #ddd", - "borderBottom": "1px solid #ddd", - "padding": "5px 10px 10px 10px", - "fontSize": (conf.fontSize + 1) - }); - this.naviTrace = $(create("div")).css({ - "width": conf.totalWidth - conf.naviBackWidth - 50, - "marginTop": "5px" - }); - - this.naviBack = $(create("div")).html("返回上层").css({ - "width": conf.naviBackWidth, - "float": "right", - "backgroundColor": "#f4f4f4", - "paddingTop": "4px", - "paddingBottom": "4px", - "border": "1px solid #ddd", - "borderRadius": "2px", - "cursor": "pointer", - "textAlign": "center", - "visibility": "hidden" - }); - var getBack = function (stream) { - stream.cover.style.visibility = "visible"; - stream.coverMouse = undefined; - stream.getLevelSource(); - stream.reRender(); - - //hidden - stream.indicatorLine.attr({"stroke": "none"}); - stream.highlightLine.attr({"stroke": "none"}); - stream.floatTag.css({"visibility" : "hidden"}); - - stream.paths.forEach(function (d, i, array) { - d.attr({transform: "s1,0.001,0,0"}); - d.label.hide(); - d.animate({transform: "t0,0"}, 750, "linear", function () { - stream.cover.style.visibility = "hidden"; - if (typeof stream.coverMouse !== 'undefined') { - stream.indicatorLine.attr({"stroke": "#000"}); - stream.highlightLine.attr({"stroke": "white"}); - stream.floatTag.css({"visibility" : "visible"}); - $(stream.canvas.canvas).trigger("mousemove", [stream.coverMouse.x, stream.coverMouse.y]); - stream.coverMouse = undefined; - } - if (d.labelLoc.showLabel) { - d.label.show(); - } - }); - }); - }; - this.naviTrace.on("click", ".navi", {stream: this}, function (e) { - var stream = e.data.stream; - stream.level = e.target.data.level; - getBack(stream); - }); - - this.naviBack.on("click", {stream: this}, function (e) { - var stream = e.data.stream; - stream.level -= 1; - getBack(stream); - }); - this.navi.append(this.naviBack).append(this.naviTrace); - }; - - Stream.prototype.initAxis = function () { - var conf = this.defaults; - this.axis = $(create("div")).css({ - "marginTop": "0", - "borderTop": "1px solid #ddd", - "height": conf.axisHeight - }); - this.axisPaper = new Raphael(this.axis, conf.totalWidth - conf.legendWidth, conf.axisHeight); - }; - - Stream.prototype.initPercentage = function () { - var conf = this.defaults; - this.percentage = $(create("div")).css({ - "width": conf.margin[3], - "height": conf.height, - "float": "left", - "marginBottom": "0", - "borderBottom": "0", - "paddingBottom": "0" - }); - if (this.defaults.more) { - this.percentagePaper = new Raphael(this.percentage, conf.margin[3], conf.height); - } - }; - - /*! - * 创建画布 - */ - Stream.prototype.createCanvas = function () { - var conf = this.defaults; - this.initLegend(); - this.initNav(); - this.initAxis(); - this.initPercentage(); - - var container = $(create("div")).css({"position": "relative"}); - this.canvas = new Raphael(container, conf.width, conf.height);console.log(conf.width, conf.height); - this.canvasContainer = $(create("div")).css({ - "float": "left", - "width": conf.width, - "height": conf.height, - "marginBottom": "0", - "borderBottom": "0", - "paddingVottom": "0" - }).append(container); - - this.floatTag = DataV.FloatTag()(container).css({"visibility": "hidden"}); - - // cover can block stream canvas when animating to prevent some default mouse event - this.cover = $(create("div")).css({ - "position": "absolute", - "width": conf.width, - "height": conf.height, - "zIndex": 100, - "visibility": "hidden" - }).bind("mousemove", {stream: this}, function (e) { - var stream = e.data.stream; - stream.coverMouse = {x: e.pageX, y: e.pageY}; - }).bind("mouseleave", {stream: this}, function (e) { - var stream = e.data.stream; - stream.coverMouse = undefined; - }); - - this.middleContainer = $(create("div")).css({ - "height": conf.height - }) - .append(this.percentage) - .append(this.canvasContainer) - .append(this.cover); - if (this.defaults.more) { - this.percentagePaper = new Raphael(this.percentage, conf.margin[3], conf.height); - } - - this.rightContainer = $(create("div")).css({ - "float": "right", - "width": conf.totalWidth - conf.legendWidth - }).append(this.navi) - .append(this.middleContainer) - .append(this.axis); - - $(this.node).css({ - "position": "relative", - "width": conf.totalWidth - }).append(this.rightContainer).append(this.leftContainer); - }; - - /** - * 设置自定义选项 - */ - Stream.prototype.setOptions = function (options) { - _.extend(this.defaults, options); - - var conf = this.defaults; - if (options && options.width) { - conf.totalWidth = conf.width; - conf.width = conf.totalWidth - conf.margin[1] - conf.margin[3] - conf.legendWidth; - if (!options.height) { - conf.autoHeight = true; - conf.height = conf.width * conf.heightWidthRatio; - } else { - conf.autoHeight = false; - } - } - }; - /*! - * 排序 - * @param {Array} source 待排序的二维数组 - */ - Stream.prototype.sort = function (source) { - var i, j, l, ll; - var rowSum = []; - var columnSum = []; - var newSource = []; - var rowName = []; - var that = this; - - for (j = 0, ll = source[0].length; j < ll; j++) { - columnSum[j] = 0; - } - - for (i = 0, l = source.length; i < l; i++) { - rowSum[i] = 0; - for (j = 0, ll = source[0].length; j < ll; j++) { - rowSum[i] += source[i][j]; - columnSum[j] += source[i][j]; - } - rowSum[i] = [rowSum[i]]; - rowSum[i].index = i; - } - - rowSum.sort(function (a, b) { - return b[0] - a[0]; - }); - - rowSum.forEach(function (d, i) { - newSource[i] = source[d.index]; - if (that.rowName) { - rowName[i] = that.rowName[d.index]; - } - }); - - for (i = 0, l = rowSum.length; i < l; i++) { - rowSum[i] = rowSum[i][0]; - } - - //this.mergeOthe - - this.rowName = rowName; - this.rowSum = rowSum; - this.columnSum = columnSum; - this.total = d3.sum(this.rowSum); - - return newSource; - }; - - /*! - * 合并数据 - */ - Stream.prototype.mergeOther = function (source) { - //change digitData, rowSum, rowName; - }; - - /** - * 获取数据 - * @param {Array} source 从二维数组中,获取纯数据的部分(排除掉列名后) - */ - Stream.prototype.getDigitData = function (source) { - //get first column name, row name and digitData; - var conf = this.defaults, - firstRow = source[0], - firstColumn, - digitData; - - var i, j, l, ll; - - firstColumn = source.map(function (d) { - return d[0]; - }); - - if (this.hasRowName()) { - if (this.hasColumnName()) { - //row names, column names - this.rowName = firstColumn.slice(1); - this.columnName = firstRow.slice(1); - digitData = source.map(function (d) { - return d.slice(1); - }).slice(1); - } else { - //row names, no column names - this.rowName = firstColumn; - this.columnName = undefined; - digitData = source.map(function (d) { - return d.slice(1); - }); - } - } else { - if (this.hasColumnName()) { - //no row names, column names - this.rowName = undefined; - this.columnName = firstRow; - digitData = source.slice(1); - } else { - //no row names, no column names - if (conf.columnNameUsed === "auto" && conf.rowNameUsed === "auto" && !DataV.isNumeric(source[0][0])) { - throw new Error("Please specify whether there are column names or row names"); - } - this.rowName = undefined; - this.columnName = undefined; - digitData = source; - } - } - for (i = 0, l = digitData.length; i < l; i++) { - for (j = 0, ll = digitData[0].length; j < ll; j++) { - digitData[i][j] = parseFloat(digitData[i][j]); - } - } - return digitData; - }; - - /** - * 获取信息数据 - */ - Stream.prototype.getInfo = function () { - var allInfos = []; - var i, j, l, ll; - var infos, info; - var column; - var digitData = this.digitData; - var descending = function (a, b) { - return b.value - a.value; - }; - for (i = 0, l = this.digitData.length; i < l; i++) { - infos = allInfos[i] = []; - infos.ratio = this.rowSum[i] / this.total; - infos.value = this.rowSum[i]; - infos.name = this.rowName[i]; - infos.id = i; - } - for (i = 0, l = digitData.length; i < l; i++) { - column = []; - for (j = 0, ll = digitData[0].length; j < ll; j++) { - allInfos[i][j] = column[j] = { - "date": this.columnName[j], - "id": i, - "name": allInfos[i].name, - "tip": "" + allInfos[i].name + "
占比:" - + (Math.round(digitData[i][j] / this.columnSum[j] * 10000) / 100) + "%
", - "total": allInfos[i].ratio, - //"value": columnTotal[i] - "value" : digitData[i][j], - "index" : j, - "rowInfo" : allInfos[i], - "ratio" : digitData[i][j] / this.columnSum[j] - }; - } - - column.sort(descending); - - for (j = 0, ll = column.length; j < ll; j++) { - column[j].rank = j; - } - } - return allInfos; - }; - - /** - * 设置数据源 - * Examples: - * 例如下面的数组表示2个人在一年4个季度的消费。第一个人在4个季度里消费了1、2、3、9元。第二个人消费了3、4、6、3元。 - * ``` - * [ - * [1,2,3,9], - * [3,4,6,3] - * ] - * ``` - * @param {Array} source 二维数组的数据源 - */ - Stream.prototype.setSource = function (source) { - this.rawData = source; - this.digitData = this.getDigitData(this.rawData); - - //get date, sort and allInfos; - //date - this.date = source[0].slice(1, source[0].length); - this.timeRange = [0, this.date.length - 1]; - //sort - this.digitData = this.sort(this.digitData); - this.allInfos = this.getInfo(this.digitData); - - this.level = 0; - this.getLevelSource(); - //this.source = this.remapSource(digitData); - this.canAnimate = false; - }; - - /** - * If useString is true, start and end are date string, else start and end are index number - * @param {Number|String} start 起始范围 - * @param {Number|String} end 结束范围 - * @param {Boolean} useString 是否是字符串 - */ - Stream.prototype.setTimeRange = function (start, end, useString) { - var idx1, idx2; - if (useString) { - idx1 = _.indexOf(this.date, start); - idx2 = _.indexOf(this.date, end); - } else { - idx1 = start; - idx2 = end; - } - - var min = Math.min(idx1, idx2); - var max = Math.max(idx1, idx2); - if (min === max) { - throw new Error("start index and end index can not be same."); - } - if (min < 0 || max > this.date.length - 1) { - throw new Error("start index or end index is beyond the time range."); - } - - this.timeRange = [min, max]; - this.getLevelSource(); - }; - - /*! - * 根据时间范围获取数据 - */ - Stream.prototype.getDataByTimeRange = function () { - if (this.timeRange[0] === 0 && this.timeRange[1] === this.date.length - 1) { - return this.digitData; - } else { - var tr = this.timeRange; - return _.map(this.digitData, function (d, i) { - return d.slice(tr[0], tr[1] + 1); - }); - } - }; - - /*! - * 获取等级数据 - */ - Stream.prototype.getLevelSource = function () { - var data = this.getDataByTimeRange(),//this.digitData, - rowStart = this.level * (this.defaults.max - 1), - rowEnd, - needMoreRow, - column = data[0].length, - remap = [], - infos = [], - moreRowInfo = []; - - if (column < 1) { - throw new Error("Data source is empty."); - } - if (this.defaults.more) { - if (rowStart + this.defaults.max >= data.length) { - if (rowStart + this.defaults.max === data.length && this.allInfos[data.length - 1][0].id === -2) { - //last more sum < this.defaults.other - rowEnd = rowStart + this.defaults.max - 1; - needMoreRow = true; - } else { - rowEnd = data.length; - needMoreRow = false; - } - } else { - rowEnd = rowStart + this.defaults.max - 1; - needMoreRow = true; - } - } else { - rowStart = 0; - rowEnd = data.length; - needMoreRow = false; - } - for (i = rowStart; i < rowEnd; i++) { - k = i - rowStart; - remap[k] = []; - for (j = 0; j < column; j++) { - remap[k][j] = {}; - remap[k][j].x = j; - remap[k][j].y = parseFloat(data[i][j]); - } - if (this.timeRange[0] === 0 && this.timeRange[1] === this.date.length - 1) { - infos[k] = this.allInfos[i]; - } else { - infos[k] = this.allInfos[i].slice(this.timeRange[0], this.timeRange[1] + 1); - } - } - if (needMoreRow) { - if (rowStart + this.defaults.max === data.length && this.allInfos[data.length - 1][0].id === -2) { - //last more sum < this.defaults.other - var valueArray = data[data.length - 1]; - moreRow = []; - for (j = 0; j < column; j++) { - moreRow[j] = {}; - moreRow[j].x = j; - moreRow[j].y = valueArray[j]; - } - moreRowInfo = this.allInfos[data.length - 1]; - } else { - moreRow = []; - for (j = 0; j < column; j++) { - moreSum = 0; - totalSum = 0; - for (m = data.length - 1; m >= rowEnd; m--) { - moreSum += parseFloat(data[m][j]); - totalSum += parseFloat(this.allInfos[m][j].total); - } - moreRow[j] = {}; - moreRow[j].x = j; - moreRow[j].y = moreSum; - moreRowInfo[j] = { - "date": this.allInfos[0][j].date, - "id": -1,// -1 clickable; -2 not click - "name": "更多", - "tip": "更多
占比:" + (Math.round(moreSum * 10000) / 100) + "%
点击查看更多信息
", - "total": totalSum, - "value": moreSum - }; - } - } - remap = [moreRow].concat(remap); - infos = [moreRowInfo].concat(infos); - } - this.infos = infos; - this.source = remap; - }; - - /** - * 生成布局 - */ - Stream.prototype.layout = function () { - var conf = this.defaults; - d3.layout.stack().offset(conf.offset).order(conf.order)(this.source); - }; - - /** - * 根据选择方案获取颜色数据 - * @param {Object} colorJson 颜色方案 - * @return {Array} 返回颜色数据 - */ - Stream.prototype.getColor = function (colorJson) { - var colorMatrix = DataV.getColor(); - var color; - var colorStyle = colorJson || {}; - var colorMode = colorStyle.mode || 'default'; - var i, l; - - switch (colorMode) { - case "gradient": - l = this.source.length; - var colorL = Math.round(l / 5); - if (colorL > colorMatrix.length - 1) { - colorL = colorMatrix.length - 1; - } - var testColor = [colorMatrix[0][0], colorMatrix[colorL][0]]; - var test1 = DataV.gradientColor(testColor, "special"); - var testColorMatrix = []; - var testColorMatrix1 = []; - for (i = 0; i < l; i++) { - testColorMatrix.push([test1(i / (l - 1)), test1(i / (l - 1))]); - } - - for (i = (l - 1); i >= 0; i--) { - testColorMatrix1.push(testColorMatrix[i]); - } - - colorMatrix = testColorMatrix; - - break; - case "random": - case "default": - break; - } - - 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); - - return color; - }; - - /** - * 生成路径 - */ - Stream.prototype.generatePaths = function () { - this.createNavi(); - this.createPercentage(); - this.createAxis(); - this.createStreamPaths(); - this.createLegend(); - }; - - /** - * 创建图例 - */ - Stream.prototype.createLegend = function () { - var conf = this.defaults, - legends = [], - m = [10, 20, 10, 20], - left = m[3], - top = m[0], - lineHeight = 25, - legendInterval = 10, - width = conf.legendWidth - conf.legendIndent, - r0 = 5, - r1 = 7, - circleW = 18, - color = this.getColor({mode: conf.colorMode}), - leftHeight, - legendHeight, - legendTopMargin, - hoverIn = function (e) { - var index = e.data.index; - var stream = e.data.stream; - var path = stream.paths[index]; - //stream.legends[stream.preIndex] - stream.preIndex = index; - stream.legends[index].css({"background": "#dddddd"}); - path.attr({"opacity": 0.5}); - }, - hoverOut = function (e) { - var index = e.data.index; - var stream = e.data.stream; - var path = stream.paths[index]; - stream.preIndex = index; - stream.legends[index].css({"background": "white"}); - path.attr({"opacity": 1.0}); - }; - - var ul = $("
    ").css({ - "margin": "0 0 0 10px", - "paddingLeft": "0" - }); - this.legend.append(ul); - - for (i = 0, l = this.infos.length; i < l; i++) { - li = $('
  • ' + this.infos[i][0].name + "
  • "); - li.css({ - "list-style-type": "square", - "list-style-position": "inside", - "color": color(i), - "white-space": "nowrap", - "padding-left": 5 - }).mouseenter({"index": i, "stream": this}, hoverIn) - .mouseleave({"index": i, "stream": this}, hoverOut); - ul.append(li); - legends.push(li); - } - this.legends = legends; - - //height and margin - leftHeight = this.rightContainer.height(); - legendHeight = this.legend.height(); - this.leftContainer.css({ - "height": leftHeight - }); - this.legend.css({"marginTop": leftHeight > legendHeight ? leftHeight - legendHeight - 30 : 0}); - }; - - /** - * 创建导航 - */ - Stream.prototype.createNavi = function () { - if (!this.defaults.more) { - this.navi.css({ - "visibility": "hidden", - "position": "absolute" - }); - } else { - this.navi.css({ - "visibility": "visible", - "position": "relative" - }); - } - this.naviTrace.empty(); - for (var i = 0; i <= this.level; i++) { - this.naviTrace.append($(" > ")); - var span = $(create("span")).html("第" + (i + 1) + "层"); - span.data = {level: i}; - this.naviTrace.append(span); - if (i !== this.level) { - span.css({"cursor": "pointer", "color": "#1E90FF"}).addClass("navi"); - } - } - this.naviBack.css("visibility", this.level > 0 ? "visible" : "hidden"); - }; - - /** - * 获取最大百分比 - */ - Stream.prototype.getMaxPercentage = function () { - this.maxPercentage = this.allInfos.reduce(function (a, b, i, array) { - return [{total: a[0].total + b[0].total}]; - })[0].total; - return this.maxPercentage; - }; - - /** - * 生成百分比数据 - */ - Stream.prototype.createPercentage = function () { - if (!this.defaults.more) { - return; - } - var conf = this.defaults; - if (this.firstRender) { - this.getMaxPercentage(); - } - var maxY = this.getMaxY() / this.maxPercentage; - - var y = maxY > 0.1 ? (1 - maxY) * conf.height + conf.fontSize * 2 / 3 - : (1 - maxY) * conf.height - conf.fontSize * 2 / 3; - - if (this.firstRender) { - this.percentageRect = this.percentagePaper.rect(0, (1 - maxY) * conf.height, conf.margin[3], maxY * conf.height) - .attr({"fill": "#f4f4f4", "stroke": "#aaa", "stroke-width": 0.5}); - this.percentageText = this.percentagePaper.text( - conf.margin[3] / 2, - y, - Math.round(maxY * 100) + "%" - ).attr({"text-anchor": "middle"}); - } else { - this.percentageRect.animate({"y": (1 - maxY) * conf.height, "height": maxY * conf.height}, 750); - this.percentageText.attr({"text": Math.round(maxY * 100) + "%"}) - .animate({"y": y}, 750); - } - }; - - /** - * 生成Stream路径 - */ - Stream.prototype.createStreamPaths = function () { - var canvas = this.canvas, - paths = [], - labels = [], - area = this.generateArea(), - color = this.getColor({mode: this.defaults.colorMode}), - conf = this.defaults, - i, - l, - _area, - pathLegend, - path, - pathLegendMouseOver = function () { - var path = this.path, - anchorIndex = path.index; - path.paths.forEach(function (d, i, array) { - if (i !== anchorIndex) { - array[i].attr({"fill": d3.interpolateRgb.apply(null, [array[i].color, "#fff"])(0.5)}); - } - }); - this.style.backgroundColor = d3.interpolateRgb.apply(null, [path.color, "#fff"])(0.8); - }, - - pathLegendMouseOut = function () { - var path = this.path, - anchorIndex = path.index; - path.paths.forEach(function (d, i, array) { - if (i !== anchorIndex) { - array[i].attr({"fill": array[i].color}); - } - }); - path.legend.style.backgroundColor = path.color; - }, - - getLabelLocation = function (locArray, el) { - var x = 0, - y = 0, - i; - var ratioMargin = 0.15; - var index = 0; - var max = 0; - var box = el.getBBox(); - var xInterval; - var minTop, maxBottom; - var showLabel = true; - var loc; - var height; - - xInterval = Math.ceil(box.width / (locArray[1].x - locArray[0].x) / 2); - if (xInterval === 0) { - xInterval = 1; - } - - locArray.forEach(function (d, i, array) { - var m = Math.max(ratioMargin * array.length, xInterval); - if (i >= m && i <= array.length - m) { - if (d.y > max) { - minTop = d.y0 - d.y; - maxBottom = d.y0; - max = d.y; - index = i; - } - } - }); - for (i = index - xInterval; i <= index + xInterval; i++) { - if (i < 0 || i >= locArray.length) { - height = 0; - showLabel = false; - break; - //return; - } - loc = locArray[i]; - //top's y is small - if (loc.y0 - loc.y > minTop) { - minTop = loc.y0 - loc.y; - } - if (loc.y0 < maxBottom) { - maxBottom = loc.y0; - } - } - - if (showLabel && maxBottom - minTop >= box.height * 0.8) { - x = locArray[index].x; - y = (minTop + maxBottom) / 2; - //y = locArray[index].y0 - locArray[index].y / 2; - } else { - showLabel = false; - } - - return {x: x, - y: y, - showLabel: showLabel}; - }, - - getLabelLocation_old2 = function (locArray, conf) { - var x, y, height = 0, i; - var indexMargin = Math.min(conf.indexMargin, Math.floor((locArray.length - 1) / 2)); - var ratioMargin = 0.15; - var index = indexMargin; - var max = 0; - if (locArray.length >= conf.indexMargin * 2 + 1) { - locArray.forEach(function (d, i, array) { - var m = Math.max(indexMargin, ratioMargin * array.length); - if (i >= m && i <= array.length - m) { - if (d.y > max) { - max = d.y; - index = i; - } - } - }); - x = locArray[index].x; - y = locArray[index].y0 - locArray[index].y / 2; - for (i = index - indexMargin; i <= index + indexMargin; i++) { - height += locArray[i].y; - } - height = height / (2 * indexMargin + 1); - } else { - x = -100; - y = -100; - height = 0; - } - - return { - x: x, - y: y, - height: height - }; - }; - - canvas.rect(0, 0, conf.width, conf.height) - .attr({"stroke": "none", - "fill": "#e0e0e0"}); - for (i = 0, l = this.source.length; i < l; i++) { - _area = area(this.pathSource[i]); - path = canvas.path(_area).attr({fill: color(i), - stroke: color(i), - "stroke-width": 1, - "stroke-linejoin": "round", - "transform": "t0," + conf.topInterval - }); - path.color = color(i); - path.index = i; - path.info = this.infos[i]; - - path.paths = paths; - path.topTrans = conf.topInterval; - path.bottomTrans = conf.bottomInterval; - path.stream = this; - - path.node.streamPath = path; - path.node.setAttribute("class", "streamPath rvml"); - - paths[path.index] = path; - } - - //label - for (i = 0, l = paths.length; i < l; i++) { - path = paths[i]; - path.label = this.canvas.text(0, 0, - conf.pathLabel ? - path.info[0].name + " " + (Math.round(path.info[0].total * 10000) / 100) + "%" : "") - .attr({"text-anchor": "middle", - "fill": "white", - "font-size": conf.fontSize, - "font-family": "微软雅黑"}); - path.labelLoc = getLabelLocation(this.pathSource[i], path.label); - - if (path.labelLoc.showLabel) { - path.label.attr({"x": path.labelLoc.x, - "y": path.labelLoc.y}); - } else { - path.label.attr({"opacity": 0}); - //path.labelOpacity = 1; - } - if (i === 0 && path.info[0].id === -1) { - path.attr({"cursor": "pointer"}); - path.label.attr({"cursor": "pointer"}); - } - labels.push(path.label); - path.label.node.setAttribute("class", "streamPath rvml"); - } - - $(this.canvas.canvas).unbind(); - - var mouseenter = function (e) { - var stream = e.data.stream; - stream.indicatorLine.attr({"stroke": "#000"}); - stream.highlightLine.attr({"stroke": "white"}); - stream.floatTag.css({"visibility" : "visible"}); - stream.axisPopText.show(); - stream.axisPopBubble.show(); - }; - - var mouseleave = function (e) { - var stream = e.data.stream, - circle; - stream.indicatorLine.attr({"stroke": "none"}); - stream.highlightLine.attr({"stroke": "none"}); - stream.floatTag.css({"visibility" : "hidden"}); - stream.axisPopText.hide(); - stream.axisPopBubble.hide(); - //recover prepath; - if (typeof stream.prePath !== 'undefined') { - stream.prePath.attr({"opacity": 1, "stroke-width": 1}); - // set legend - //circle = stream.legends[stream.prePath.index].circle; - //circle.attr({"r": circle.data("r0"), "opacity": 1}); - stream.legends[stream.prePath.index].css({"background": "white"}); - stream.prePath = undefined; - } - }; - - var click = function (e) { - var stream = e.data.stream, - position; - if (typeof stream.prePath !== 'undefined' && stream.prePath.info[0].id === -1) { - //hidden - stream.indicatorLine.attr({"stroke": "none"}); - stream.highlightLine.attr({"stroke": "none"}); - stream.floatTag.css({"visibility" : "hidden"}); - - stream.level += 1; - - //set cover - position = $(this).parent().position(); - stream.cover.css({ - left: position.left, - top: position.top, - visibility: "visible" - }); - stream.coverMouse = {x: e.pageX, y: e.pageY}; - - //redraw - stream.getLevelSource(); - stream.reRender(); - - //hidden - stream.indicatorLine.attr({"stroke": "none"}); - stream.highlightLine.attr({"stroke": "none"}); - stream.floatTag.css({"visibility" : "hidden"}); - - stream.paths.forEach(function (d, i, array) { - d.attr({transform: "s1,0.001,0," + stream.defaults.height}); - d.label.hide(); - d.animate({transform: "t0,0"}, 750, "linear", function () { - stream.cover.style.visibility = "hidden"; - if (typeof stream.coverMouse !== 'undefined') { - stream.indicatorLine.attr({"stroke": "#000"}); - stream.highlightLine.attr({"stroke": "white"}); - stream.floatTag.css({"visibility" : "visible"}); - $(stream.canvas.canvas).trigger("mousemove", - [stream.coverMouse.x, stream.coverMouse.y]); - stream.coverMouse = undefined; - } - //if (d.labelOpacity === 1) - if (d.labelLoc.showLabel) { - d.label.show(); - } - }); - }); - } - }; - - var mousemove = function (e, pageX, pageY) { - var stream = e.data.stream; - var offset = $(this).parent().offset(); - var position = $(this).parent().position(); - //var offset = $(this).offset(); - var x = (e.pageX || pageX) - offset.left, - y = (e.pageY || pageY) - offset.top; - var floatTag, - floatTagWidth, - floatTagHeight, - mouseToFloatTag = {x: 20, y: 20}; - var path, - pathSource = stream.pathSource, - pathSourceP, - pathIndex, - circle; - var i, l; - var xIdx = Math.floor((x / (stream.defaults.width / (stream.source[0].length - 1) / 2) + 1) / 2); - var pathsourceP, - lineX; - - //get path - path = undefined; - pathSource = stream.pathSource; - for (i = 0, l = pathSource.length; i < l; i++) { - if (y >= pathSource[i][xIdx].y0 - pathSource[i][xIdx].y && y <= pathSource[i][xIdx].y0) { - path = stream.paths[i]; - pathIndex = i; - break; - } - } - if (typeof path === 'undefined') { - return; - } - - //recover prepath; - if (typeof stream.prePath !== 'undefined') { - stream.prePath.attr({"opacity": 1, "stroke-width": 1}); - // set legend - stream.legends[stream.prePath.index].css({"background": "white"}); - } - //change new path; - stream.prePath = path; - path.attr({"opacity": 0.5, "stroke-width": 0}); - - // set legend - stream.legends[stream.prePath.index].css({"background": "#dddddd"}); - - //set indicator and highlight line - lineX = stream.defaults.width * xIdx / (stream.source[0].length - 1); - pathSourceP = pathSource[pathSource.length - 1][xIdx]; - stream.indicatorLine.attr({path: "M" + lineX - + " " + (pathSourceP.y0 - pathSourceP.y) - + "V" + pathSource[0][xIdx].y0}); - - pathSourceP = pathSource[pathIndex][xIdx]; - stream.highlightLine.attr({path: "M" + lineX - + " " + (pathSourceP.y0 - pathSourceP.y) - + "V" + pathSourceP.y0}); - if (pathIndex === 0 && path.info[0].id === -1) { - stream.highlightLine.attr({"cursor": "pointer"}); - } else { - stream.highlightLine.attr({"cursor": "auto"}); - } - - floatTag = stream.floatTag; - floatTag.html(path.info[xIdx].tip); - - //axis pop bubble - stream.axisPopText.attr({"text": stream.date[xIdx + stream.timeRange[0]]}) - .transform("t" + (lineX + stream.defaults.margin[3]) + ",0"); - stream.axisPopBubble.transform("t" + (lineX + stream.defaults.margin[3]) + ",0"); - - //customevent; - if (stream.defaults.customEventHandle.mousemove) { - stream.defaults.customEventHandle.mousemove.call(stream, - {"timeIndex": xIdx, "pathIndex": pathIndex}); - } - }; - var $canvas = $(this.canvas.canvas); - $canvas.bind("mouseenter", {"stream": this}, mouseenter); - $canvas.bind("mouseleave", {"stream": this}, mouseleave); - $canvas.bind("click", {"stream": this}, click); - $canvas.bind("mousemove", {"stream": this}, mousemove); - - this.paths = paths; - this.labels = labels; - this.indicatorLine = canvas.path("M0 " + conf.topInterval + "V" + (conf.height - conf.bottomInterval)) - .attr({stroke: "none", "stroke-width": 1, "stroke-dasharray": "- "}); - this.highlightLine = canvas.path("M0 " + conf.topInterval + "V" + (conf.height - conf.bottomInterval)) - .attr({stroke: "none", "stroke-width": 2}); - }; - - /** - * 创建坐标轴 - */ - Stream.prototype.createAxis = function () { - //all date strings' format are same, string length are same - var conf = this.defaults, - date = this.date.slice(this.timeRange[0], this.timeRange[1] + 1), - left = conf.margin[3], - //left = conf.margin[3] + conf.legendWidth, - right = conf.totalWidth - conf.margin[1] - conf.legendWidth, - tempWord, - tickNumber, - getPopPath = function (El) { - //down pop - var x = 0, - y = 0, - size = 4, - cw = 23, - bb = {height: 8}; - if (El) { - bb = El.getBBox(); - bb.height *= 0.6; - cw = bb.width / 2 - size; - } - return [ - 'M', x, y, - 'l', size, size, cw, 0, - 'a', size, size, 0, 0, 1, size, size, - 'l', 0, bb.height, - 'a', size, size, 0, 0, 1, -size, size, - 'l', -(size * 2 + cw * 2), 0, - 'a', size, size, 0, 0, 1, -size, -size, - 'l', 0, -bb.height, - 'a', size, size, 0, 0, 1, size, -size, - 'l', cw, 0, - 'z' - ].join(','); - }; - - this.dateScale = d3.scale.linear() - .domain([0, date.length - 1]) - .range([left, right]); - - tempWord = this.axisPaper.text(0, 0, date[0]); - tickNumber = Math.floor((right - left) / tempWord.getBBox().width / 2) + 1; - tempWord.remove(); - //tickNumber = 4; - - Axis().scale(this.dateScale) - .ticks(tickNumber) - //.ticks(conf.axisTickNumber) - .tickSize(6, 3, 3) - .tickAttr({"stroke": "none"}) - .minorTickAttr({"stroke": "none"}) - .domainAttr({"stroke": "none"}) - //.tickTextAttr({"font-size": conf.fontSize}) - .tickFormat(function (d) { - return date[d] || ""; - })(this.axisPaper); - - this.axisPopText = this.axisPaper.text(0, 11, date[0]) - .attr({ "text-anchor": "middle", - "fill": "#fff", - //"font-size": conf.fontSize, - "transform": "t" + left + ",0"}) - .hide(); - this.axisPopBubble = this.axisPaper.path(getPopPath(this.axisPopText)) - .attr({ "fill": "#000", - //"opacity": 0, - "transform": "t" + left + ",0"}) - .toBack() - .hide(); - }; - - /** - * 获取纵轴最大值 - */ - Stream.prototype.getMaxY = function () { - return d3.max(this.source, function (d) { - return d3.max(d, function (d) { - return d.y0 + d.y; - }); - }); - }; - - /** - * 映射路径源 - */ - Stream.prototype.mapPathSource = function () { - var conf = this.defaults, - maxX = this.source[0].length - 1,//this.digitData[0].length - 1, - maxY = this.getMaxY(), - width = conf.width, - height = conf.height - conf.topInterval - conf.bottomInterval; - var i, j, l, l2, s, ps; - this.pathSource = []; - for (i = 0, l = this.source.length; i < l; i++) { - this.pathSource[i] = []; - for (j = 0, l2 = this.source[0].length; j < l2; j++) { - s = this.source[i][j]; - ps = this.pathSource[i][j] = {}; - ps.x = s.x * width / maxX; - ps.y0 = height - s.y0 * height / maxY; - ps.y = s.y * height / maxY; - } - } - }; - - /** - * 生成区域 - */ - Stream.prototype.generateArea = function () { - this.mapPathSource(); - return d3.svg.area().x(function (d) { - return d.x; - }).y0(function (d) { - return d.y0; - }).y1(function (d) { - return d.y0 - d.y; - }); - }; - - /*! - * 生成区域 - */ - Stream.prototype.generateArea_old = function () { - var conf = this.defaults, - maxX = this.digitData[0].length - 1, - maxY = this.getMaxY(), - width = conf.width, - height = conf.height - conf.topInterval - conf.bottomInterval, - area = d3.svg.area() - .x(function (d) { - return d.x * width / maxX; - }) - .y0(function (d) { - return height - d.y0 * height / maxY; - }) - .y1(function (d) { - return height - (d.y + d.y0) * height / maxY; - }); - return area; - }; - - /** - * 清除画布 - */ - Stream.prototype.clearCanvas = function () { - this.canvas.clear(); - this.legend.empty(); - this.axisPaper.clear(); - }; - - /** - * 重绘图表 - */ - Stream.prototype.reRender = function (options) { - this.setOptions(options); - this.clearCanvas(); - this.layout(); - this.generatePaths(); - this.canAnimate = true; - }; - - /** - * 绘制图表 - */ - Stream.prototype.render = function (options) { - this.firstRender = true; - this.setOptions(options); - this.clearCanvas(); - this.layout(); - this.generatePaths(); - this.firstRender = false; - this.canAnimate = true; - }; - - /** - * 重设图表 - */ - Stream.prototype.resize = function (options) { - var conf = this.defaults; - - if (!options.width && !options.height) { - throw new Error("no width and height input"); - } else if (options.width && !options.height) { - if (conf.autoHeight) { - this.setOptions({"width": options.width}); - } else { - this.setOptions({"width": options.width, "height": conf.height}); - } - } else if (!options.width && options.height) { - this.setOptions({"width": conf.totalWidth, "height": options.height}); - } else { - this.setOptions({"width": options.width, "height": options.height}); - } - - this.node.innerHTML = ""; - this.createCanvas(); - this.reRender(); - }; - - /** - * 侦听自定义事件 - * @param {String} eventName 事件名 - * @param {Function} callback 事件回调函数 - */ - Stream.prototype.on = function (eventName, callback) { - if (typeof this.defaults.customEventHandle[eventName] !== 'undefined') { - this.defaults.customEventHandle[eventName] = callback; - } - }; - - /** - * 设置动画 - * @param {Object} options 选项对象 - * @param {Number} timeDuration 时间 - */ - Stream.prototype.animate = function (options, timeDuration) { - //must after render if new Source has been set; - if (!this.canAnimate) { - throw new Error("Function animate must be called after render if new Source has been set."); - } - var time = 0, - area, - color, - i, - l; - if (arguments.length > 1) { - time = timeDuration; - } - - //this.setOptions(options); - if (options.offset || options.order) { - this.source = this.remapSource(this.digitData); - this.layout(); - } - area = this.generateArea(); - color = this.getColor(); - for (i = 0, l = this.source.length; i < l; i++) { - var _area = area(this.source[i]); - var anim = Raphael.animation({path: _area, fill: color(i)}, time); - this.paths[i].animate(anim); - } - }; - - /*! - * 导出Stream - */ - return Stream; -}); +/*global Raphael, d3, $, define, _ */ +/*! + * Stream的兼容定义 + */ +;(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];}); + } +})('Stream', function (require) { + var DataV = require('DataV'); + var Axis = require('Axis'); + var create = function (tag) { + return document.createElement(tag); + }; + + /* + * Stream构造函数 + * Create stream in a dom node with id "chart", width is 500; height is 600px; + * Options: + * + * - `width` 宽度,默认为节点宽度 + * + * Examples: + * ``` + * var stream = new Stream("chart", {"width": 500, "height": 600}); + * ``` + * @param {Mix} node The dom node or dom node Id + * @param {Object} options options json object for determin stream style. + */ + var Stream = DataV.extend(DataV.Chart, { + initialize: function (node, options) { + this.type = "Stream"; + this.node = this.checkContainer(node); + + this.level = 0; + // Properties + this.defaults.offset = "zero";//"expand"; + this.defaults.order = "default"; + this.defaults.columnNameUsed = "auto"; + this.defaults.rowNameUsed = "auto"; + this.defaults.topInterval = 0; + this.defaults.bottomInterval = 0; + this.defaults.legend = true; + this.defaults.axis = true; + this.defaults.pathLabel = true; + this.defaults.fontSize = 12; + this.defaults.heightWidthRatio = 0.618; + //this.defaults.axisTickNumber = 8; // axis ticks number + + this.defaults.indexMargin = 3; // if dates.length < indexMargin * 2 + 1, do not show label + + this.defaults.more = true; + this.defaults.max = 20; + this.defaults.other = 0.1; + + this.timeRange = []; + // Canvas + this.defaults.width = 750; + this.defaults.height = 360; + this.defaults.totalWidth = 820; + this.defaults.naviBackWidth = 80; + this.defaults.legendHeight = 50; + this.defaults.legendWidth = 150; + this.defaults.legendIndent = 21; + this.defaults.axisHeight = 30; + this.defaults.margin = [0, 40, 0, 40]; + + this.defaults.customEventHandle = {"mousemove": null}; + + //test related + this.defaults.testMakeup = false; + this.defaults.testDays = 30; + this.defaults.testDataType = 0; //0: random; 1: false random; 2: same; >2: small change; + + this.setOptions(options); + this.createCanvas(); + } + }); + + /** + * Stream图纬度描述 + */ + Stream.dimension = {}; + /** + * 流向纬度,例如,时间 + */ + Stream.dimension.stream = { + type: "string", + required: true + }; + /** + * 堆叠纬度,例如,按类目 + */ + Stream.dimension.stack = { + type: "string", + required: true + }; + /** + * 值纬度,在流向和堆叠纬度上的值 + */ + Stream.dimension.value = { + type: "number", + required: true + }; + + + Stream.prototype.initLegend = function () { + var conf = this.defaults; + this.legend = $(create("div")).css({ + "overflow": "hidden", + "width": conf.legendWidth - conf.legendIndent, + "padding": "10px 0 10px 0" + }); + this.leftContainer = $(create("div")).css({ + "width": conf.legendWidth - 4, + "overflow-x": "hidden" + }).append(this.legend); + }; + + Stream.prototype.initNav = function () { + var conf = this.defaults; + this.navi = $(create("div")).css({ + "borderTop": "1px solid #ddd", + "borderBottom": "1px solid #ddd", + "padding": "5px 10px 10px 10px", + "fontSize": (conf.fontSize + 1) + }); + this.naviTrace = $(create("div")).css({ + "width": conf.totalWidth - conf.naviBackWidth - 50, + "marginTop": "5px" + }); + + this.naviBack = $(create("div")).html("返回上层").css({ + "width": conf.naviBackWidth, + "float": "right", + "backgroundColor": "#f4f4f4", + "paddingTop": "4px", + "paddingBottom": "4px", + "border": "1px solid #ddd", + "borderRadius": "2px", + "cursor": "pointer", + "textAlign": "center", + "visibility": "hidden" + }); + var getBack = function (stream) { + stream.cover.style.visibility = "visible"; + stream.coverMouse = undefined; + stream.getLevelSource(); + stream.reRender(); + + //hidden + stream.indicatorLine.attr({"stroke": "none"}); + stream.highlightLine.attr({"stroke": "none"}); + stream.floatTag.css({"visibility" : "hidden"}); + + stream.paths.forEach(function (d, i, array) { + d.attr({transform: "s1,0.001,0,0"}); + d.label.hide(); + d.animate({transform: "t0,0"}, 750, "linear", function () { + stream.cover.style.visibility = "hidden"; + if (typeof stream.coverMouse !== 'undefined') { + stream.indicatorLine.attr({"stroke": "#000"}); + stream.highlightLine.attr({"stroke": "white"}); + stream.floatTag.css({"visibility" : "visible"}); + $(stream.canvas.canvas).trigger("mousemove", [stream.coverMouse.x, stream.coverMouse.y]); + stream.coverMouse = undefined; + } + if (d.labelLoc.showLabel) { + d.label.show(); + } + }); + }); + }; + this.naviTrace.on("click", ".navi", {stream: this}, function (e) { + var stream = e.data.stream; + stream.level = e.target.data.level; + getBack(stream); + }); + + this.naviBack.on("click", {stream: this}, function (e) { + var stream = e.data.stream; + stream.level -= 1; + getBack(stream); + }); + this.navi.append(this.naviBack).append(this.naviTrace); + }; + + Stream.prototype.initAxis = function () { + var conf = this.defaults; + this.axis = $(create("div")).css({ + "marginTop": "0", + "borderTop": "1px solid #ddd", + "height": conf.axisHeight + }); + this.axisPaper = new Raphael(this.axis, conf.totalWidth - conf.legendWidth, conf.axisHeight); + }; + + Stream.prototype.initPercentage = function () { + var conf = this.defaults; + this.percentage = $(create("div")).css({ + "width": conf.margin[3], + "height": conf.height, + "float": "left", + "marginBottom": "0", + "borderBottom": "0", + "paddingBottom": "0" + }); + if (this.defaults.more) { + this.percentagePaper = new Raphael(this.percentage, conf.margin[3], conf.height); + } + }; + + /*! + * 创建画布 + */ + Stream.prototype.createCanvas = function () { + var conf = this.defaults; + this.initLegend(); + this.initNav(); + this.initAxis(); + this.initPercentage(); + + var container = $(create("div")).css({"position": "relative"}); + this.canvas = new Raphael(container, conf.width, conf.height);console.log(conf.width, conf.height); + this.canvasContainer = $(create("div")).css({ + "float": "left", + "width": conf.width, + "height": conf.height, + "marginBottom": "0", + "borderBottom": "0", + "paddingVottom": "0" + }).append(container); + + this.floatTag = DataV.FloatTag()(container).css({"visibility": "hidden"}); + + // cover can block stream canvas when animating to prevent some default mouse event + this.cover = $(create("div")).css({ + "position": "absolute", + "width": conf.width, + "height": conf.height, + "zIndex": 100, + "visibility": "hidden" + }).bind("mousemove", {stream: this}, function (e) { + var stream = e.data.stream; + stream.coverMouse = {x: e.pageX, y: e.pageY}; + }).bind("mouseleave", {stream: this}, function (e) { + var stream = e.data.stream; + stream.coverMouse = undefined; + }); + + this.middleContainer = $(create("div")).css({ + "height": conf.height + }) + .append(this.percentage) + .append(this.canvasContainer) + .append(this.cover); + if (this.defaults.more) { + this.percentagePaper = new Raphael(this.percentage, conf.margin[3], conf.height); + } + + this.rightContainer = $(create("div")).css({ + "float": "right", + "width": conf.totalWidth - conf.legendWidth + }).append(this.navi) + .append(this.middleContainer) + .append(this.axis); + + $(this.node).css({ + "position": "relative", + "width": conf.totalWidth + }).append(this.rightContainer).append(this.leftContainer); + }; + + /** + * 设置自定义选项 + */ + Stream.prototype.setOptions = function (options) { + _.extend(this.defaults, options); + + var conf = this.defaults; + if (options && options.width) { + conf.totalWidth = conf.width; + conf.width = conf.totalWidth - conf.margin[1] - conf.margin[3] - conf.legendWidth; + if (!options.height) { + conf.autoHeight = true; + conf.height = conf.width * conf.heightWidthRatio; + } else { + conf.autoHeight = false; + } + } + }; + /*! + * 排序 + * @param {Array} source 待排序的二维数组 + */ + Stream.prototype.sort = function (source) { + var i, j, l, ll; + var rowSum = []; + var columnSum = []; + var newSource = []; + var rowName = []; + var that = this; + + for (j = 0, ll = source[0].length; j < ll; j++) { + columnSum[j] = 0; + } + + for (i = 0, l = source.length; i < l; i++) { + rowSum[i] = 0; + for (j = 0, ll = source[0].length; j < ll; j++) { + rowSum[i] += source[i][j]; + columnSum[j] += source[i][j]; + } + rowSum[i] = [rowSum[i]]; + rowSum[i].index = i; + } + + rowSum.sort(function (a, b) { + return b[0] - a[0]; + }); + + rowSum.forEach(function (d, i) { + newSource[i] = source[d.index]; + if (that.rowName) { + rowName[i] = that.rowName[d.index]; + } + }); + + for (i = 0, l = rowSum.length; i < l; i++) { + rowSum[i] = rowSum[i][0]; + } + + //this.mergeOthe + + this.rowName = rowName; + this.rowSum = rowSum; + this.columnSum = columnSum; + this.total = d3.sum(this.rowSum); + + return newSource; + }; + + /*! + * 合并数据 + */ + Stream.prototype.mergeOther = function (source) { + //change digitData, rowSum, rowName; + }; + + /** + * 获取数据 + * @param {Array} source 从二维数组中,获取纯数据的部分(排除掉列名后) + */ + Stream.prototype.getDigitData = function (source) { + //get first column name, row name and digitData; + var conf = this.defaults, + firstRow = source[0], + firstColumn, + digitData; + + var i, j, l, ll; + + firstColumn = source.map(function (d) { + return d[0]; + }); + + if (this.hasRowName()) { + if (this.hasColumnName()) { + //row names, column names + this.rowName = firstColumn.slice(1); + this.columnName = firstRow.slice(1); + digitData = source.map(function (d) { + return d.slice(1); + }).slice(1); + } else { + //row names, no column names + this.rowName = firstColumn; + this.columnName = undefined; + digitData = source.map(function (d) { + return d.slice(1); + }); + } + } else { + if (this.hasColumnName()) { + //no row names, column names + this.rowName = undefined; + this.columnName = firstRow; + digitData = source.slice(1); + } else { + //no row names, no column names + if (conf.columnNameUsed === "auto" && conf.rowNameUsed === "auto" && !DataV.isNumeric(source[0][0])) { + throw new Error("Please specify whether there are column names or row names"); + } + this.rowName = undefined; + this.columnName = undefined; + digitData = source; + } + } + for (i = 0, l = digitData.length; i < l; i++) { + for (j = 0, ll = digitData[0].length; j < ll; j++) { + digitData[i][j] = parseFloat(digitData[i][j]); + } + } + return digitData; + }; + + /** + * 获取信息数据 + */ + Stream.prototype.getInfo = function () { + var allInfos = []; + var i, j, l, ll; + var infos, info; + var column; + var digitData = this.digitData; + var descending = function (a, b) { + return b.value - a.value; + }; + for (i = 0, l = this.digitData.length; i < l; i++) { + infos = allInfos[i] = []; + infos.ratio = this.rowSum[i] / this.total; + infos.value = this.rowSum[i]; + infos.name = this.rowName[i]; + infos.id = i; + } + for (i = 0, l = digitData.length; i < l; i++) { + column = []; + for (j = 0, ll = digitData[0].length; j < ll; j++) { + allInfos[i][j] = column[j] = { + "date": this.columnName[j], + "id": i, + "name": allInfos[i].name, + "tip": "" + allInfos[i].name + "
    占比:" + + (Math.round(digitData[i][j] / this.columnSum[j] * 10000) / 100) + "%
    ", + "total": allInfos[i].ratio, + //"value": columnTotal[i] + "value" : digitData[i][j], + "index" : j, + "rowInfo" : allInfos[i], + "ratio" : digitData[i][j] / this.columnSum[j] + }; + } + + column.sort(descending); + + for (j = 0, ll = column.length; j < ll; j++) { + column[j].rank = j; + } + } + return allInfos; + }; + + /** + * 设置数据源 + * Examples: + * 例如下面的数组表示2个人在一年4个季度的消费。第一个人在4个季度里消费了1、2、3、9元。第二个人消费了3、4、6、3元。 + * ``` + * [ + * [1,2,3,9], + * [3,4,6,3] + * ] + * ``` + * @param {Array} source 二维数组的数据源 + */ + Stream.prototype.setSource = function (source) { + this.rawData = source; + this.digitData = this.getDigitData(this.rawData); + + //get date, sort and allInfos; + //date + this.date = source[0].slice(1, source[0].length); + this.timeRange = [0, this.date.length - 1]; + //sort + this.digitData = this.sort(this.digitData); + this.allInfos = this.getInfo(this.digitData); + + this.level = 0; + this.getLevelSource(); + //this.source = this.remapSource(digitData); + this.canAnimate = false; + }; + + /** + * If useString is true, start and end are date string, else start and end are index number + * @param {Number|String} start 起始范围 + * @param {Number|String} end 结束范围 + * @param {Boolean} useString 是否是字符串 + */ + Stream.prototype.setTimeRange = function (start, end, useString) { + var idx1, idx2; + if (useString) { + idx1 = _.indexOf(this.date, start); + idx2 = _.indexOf(this.date, end); + } else { + idx1 = start; + idx2 = end; + } + + var min = Math.min(idx1, idx2); + var max = Math.max(idx1, idx2); + if (min === max) { + throw new Error("start index and end index can not be same."); + } + if (min < 0 || max > this.date.length - 1) { + throw new Error("start index or end index is beyond the time range."); + } + + this.timeRange = [min, max]; + this.getLevelSource(); + }; + + /*! + * 根据时间范围获取数据 + */ + Stream.prototype.getDataByTimeRange = function () { + if (this.timeRange[0] === 0 && this.timeRange[1] === this.date.length - 1) { + return this.digitData; + } else { + var tr = this.timeRange; + return _.map(this.digitData, function (d, i) { + return d.slice(tr[0], tr[1] + 1); + }); + } + }; + + /*! + * 获取等级数据 + */ + Stream.prototype.getLevelSource = function () { + var data = this.getDataByTimeRange(),//this.digitData, + rowStart = this.level * (this.defaults.max - 1), + rowEnd, + needMoreRow, + column = data[0].length, + remap = [], + infos = [], + moreRowInfo = []; + + if (column < 1) { + throw new Error("Data source is empty."); + } + if (this.defaults.more) { + if (rowStart + this.defaults.max >= data.length) { + if (rowStart + this.defaults.max === data.length && this.allInfos[data.length - 1][0].id === -2) { + //last more sum < this.defaults.other + rowEnd = rowStart + this.defaults.max - 1; + needMoreRow = true; + } else { + rowEnd = data.length; + needMoreRow = false; + } + } else { + rowEnd = rowStart + this.defaults.max - 1; + needMoreRow = true; + } + } else { + rowStart = 0; + rowEnd = data.length; + needMoreRow = false; + } + for (i = rowStart; i < rowEnd; i++) { + k = i - rowStart; + remap[k] = []; + for (j = 0; j < column; j++) { + remap[k][j] = {}; + remap[k][j].x = j; + remap[k][j].y = parseFloat(data[i][j]); + } + if (this.timeRange[0] === 0 && this.timeRange[1] === this.date.length - 1) { + infos[k] = this.allInfos[i]; + } else { + infos[k] = this.allInfos[i].slice(this.timeRange[0], this.timeRange[1] + 1); + } + } + if (needMoreRow) { + if (rowStart + this.defaults.max === data.length && this.allInfos[data.length - 1][0].id === -2) { + //last more sum < this.defaults.other + var valueArray = data[data.length - 1]; + moreRow = []; + for (j = 0; j < column; j++) { + moreRow[j] = {}; + moreRow[j].x = j; + moreRow[j].y = valueArray[j]; + } + moreRowInfo = this.allInfos[data.length - 1]; + } else { + moreRow = []; + for (j = 0; j < column; j++) { + moreSum = 0; + totalSum = 0; + for (m = data.length - 1; m >= rowEnd; m--) { + moreSum += parseFloat(data[m][j]); + totalSum += parseFloat(this.allInfos[m][j].total); + } + moreRow[j] = {}; + moreRow[j].x = j; + moreRow[j].y = moreSum; + moreRowInfo[j] = { + "date": this.allInfos[0][j].date, + "id": -1,// -1 clickable; -2 not click + "name": "更多", + "tip": "更多
    占比:" + (Math.round(moreSum * 10000) / 100) + "%
    点击查看更多信息
    ", + "total": totalSum, + "value": moreSum + }; + } + } + remap = [moreRow].concat(remap); + infos = [moreRowInfo].concat(infos); + } + this.infos = infos; + this.source = remap; + }; + + /** + * 生成布局 + */ + Stream.prototype.layout = function () { + var conf = this.defaults; + d3.layout.stack().offset(conf.offset).order(conf.order)(this.source); + }; + + /** + * 根据选择方案获取颜色数据 + * @param {Object} colorJson 颜色方案 + * @return {Array} 返回颜色数据 + */ + Stream.prototype.getColor = function (colorJson) { + var colorMatrix = DataV.getColor(); + var color; + var colorStyle = colorJson || {}; + var colorMode = colorStyle.mode || 'default'; + var i, l; + + switch (colorMode) { + case "gradient": + l = this.source.length; + var colorL = Math.round(l / 5); + if (colorL > colorMatrix.length - 1) { + colorL = colorMatrix.length - 1; + } + var testColor = [colorMatrix[0][0], colorMatrix[colorL][0]]; + var test1 = DataV.gradientColor(testColor, "special"); + var testColorMatrix = []; + var testColorMatrix1 = []; + for (i = 0; i < l; i++) { + testColorMatrix.push([test1(i / (l - 1)), test1(i / (l - 1))]); + } + + for (i = (l - 1); i >= 0; i--) { + testColorMatrix1.push(testColorMatrix[i]); + } + + colorMatrix = testColorMatrix; + + break; + case "random": + case "default": + break; + } + + 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); + + return color; + }; + + /** + * 生成路径 + */ + Stream.prototype.generatePaths = function () { + this.createNavi(); + this.createPercentage(); + this.createAxis(); + this.createStreamPaths(); + this.createLegend(); + }; + + /** + * 创建图例 + */ + Stream.prototype.createLegend = function () { + var conf = this.defaults, + legends = [], + m = [10, 20, 10, 20], + left = m[3], + top = m[0], + lineHeight = 25, + legendInterval = 10, + width = conf.legendWidth - conf.legendIndent, + r0 = 5, + r1 = 7, + circleW = 18, + color = this.getColor({mode: conf.colorMode}), + leftHeight, + legendHeight, + legendTopMargin, + hoverIn = function (e) { + var index = e.data.index; + var stream = e.data.stream; + var path = stream.paths[index]; + //stream.legends[stream.preIndex] + stream.preIndex = index; + stream.legends[index].css({"background": "#dddddd"}); + path.attr({"opacity": 0.5}); + }, + hoverOut = function (e) { + var index = e.data.index; + var stream = e.data.stream; + var path = stream.paths[index]; + stream.preIndex = index; + stream.legends[index].css({"background": "white"}); + path.attr({"opacity": 1.0}); + }; + + var ul = $("
      ").css({ + "margin": "0 0 0 10px", + "paddingLeft": "0" + }); + this.legend.append(ul); + + for (i = 0, l = this.infos.length; i < l; i++) { + li = $('
    • ' + this.infos[i][0].name + "
    • "); + li.css({ + "list-style-type": "square", + "list-style-position": "inside", + "color": color(i), + "white-space": "nowrap", + "padding-left": 5 + }).mouseenter({"index": i, "stream": this}, hoverIn) + .mouseleave({"index": i, "stream": this}, hoverOut); + ul.append(li); + legends.push(li); + } + this.legends = legends; + + //height and margin + leftHeight = this.rightContainer.height(); + legendHeight = this.legend.height(); + this.leftContainer.css({ + "height": leftHeight + }); + this.legend.css({"marginTop": leftHeight > legendHeight ? leftHeight - legendHeight - 30 : 0}); + }; + + /** + * 创建导航 + */ + Stream.prototype.createNavi = function () { + if (!this.defaults.more) { + this.navi.css({ + "visibility": "hidden", + "position": "absolute" + }); + } else { + this.navi.css({ + "visibility": "visible", + "position": "relative" + }); + } + this.naviTrace.empty(); + for (var i = 0; i <= this.level; i++) { + this.naviTrace.append($(" > ")); + var span = $(create("span")).html("第" + (i + 1) + "层"); + span.data = {level: i}; + this.naviTrace.append(span); + if (i !== this.level) { + span.css({"cursor": "pointer", "color": "#1E90FF"}).addClass("navi"); + } + } + this.naviBack.css("visibility", this.level > 0 ? "visible" : "hidden"); + }; + + /** + * 获取最大百分比 + */ + Stream.prototype.getMaxPercentage = function () { + this.maxPercentage = this.allInfos.reduce(function (a, b, i, array) { + return [{total: a[0].total + b[0].total}]; + })[0].total; + return this.maxPercentage; + }; + + /** + * 生成百分比数据 + */ + Stream.prototype.createPercentage = function () { + if (!this.defaults.more) { + return; + } + var conf = this.defaults; + if (this.firstRender) { + this.getMaxPercentage(); + } + var maxY = this.getMaxY() / this.maxPercentage; + + var y = maxY > 0.1 ? (1 - maxY) * conf.height + conf.fontSize * 2 / 3 + : (1 - maxY) * conf.height - conf.fontSize * 2 / 3; + + if (this.firstRender) { + this.percentageRect = this.percentagePaper.rect(0, (1 - maxY) * conf.height, conf.margin[3], maxY * conf.height) + .attr({"fill": "#f4f4f4", "stroke": "#aaa", "stroke-width": 0.5}); + this.percentageText = this.percentagePaper.text( + conf.margin[3] / 2, + y, + Math.round(maxY * 100) + "%" + ).attr({"text-anchor": "middle"}); + } else { + this.percentageRect.animate({"y": (1 - maxY) * conf.height, "height": maxY * conf.height}, 750); + this.percentageText.attr({"text": Math.round(maxY * 100) + "%"}) + .animate({"y": y}, 750); + } + }; + + /** + * 生成Stream路径 + */ + Stream.prototype.createStreamPaths = function () { + var canvas = this.canvas, + paths = [], + labels = [], + area = this.generateArea(), + color = this.getColor({mode: this.defaults.colorMode}), + conf = this.defaults, + i, + l, + _area, + pathLegend, + path, + pathLegendMouseOver = function () { + var path = this.path, + anchorIndex = path.index; + path.paths.forEach(function (d, i, array) { + if (i !== anchorIndex) { + array[i].attr({"fill": d3.interpolateRgb.apply(null, [array[i].color, "#fff"])(0.5)}); + } + }); + this.style.backgroundColor = d3.interpolateRgb.apply(null, [path.color, "#fff"])(0.8); + }, + + pathLegendMouseOut = function () { + var path = this.path, + anchorIndex = path.index; + path.paths.forEach(function (d, i, array) { + if (i !== anchorIndex) { + array[i].attr({"fill": array[i].color}); + } + }); + path.legend.style.backgroundColor = path.color; + }, + + getLabelLocation = function (locArray, el) { + var x = 0, + y = 0, + i; + var ratioMargin = 0.15; + var index = 0; + var max = 0; + var box = el.getBBox(); + var xInterval; + var minTop, maxBottom; + var showLabel = true; + var loc; + var height; + + xInterval = Math.ceil(box.width / (locArray[1].x - locArray[0].x) / 2); + if (xInterval === 0) { + xInterval = 1; + } + + locArray.forEach(function (d, i, array) { + var m = Math.max(ratioMargin * array.length, xInterval); + if (i >= m && i <= array.length - m) { + if (d.y > max) { + minTop = d.y0 - d.y; + maxBottom = d.y0; + max = d.y; + index = i; + } + } + }); + for (i = index - xInterval; i <= index + xInterval; i++) { + if (i < 0 || i >= locArray.length) { + height = 0; + showLabel = false; + break; + //return; + } + loc = locArray[i]; + //top's y is small + if (loc.y0 - loc.y > minTop) { + minTop = loc.y0 - loc.y; + } + if (loc.y0 < maxBottom) { + maxBottom = loc.y0; + } + } + + if (showLabel && maxBottom - minTop >= box.height * 0.8) { + x = locArray[index].x; + y = (minTop + maxBottom) / 2; + //y = locArray[index].y0 - locArray[index].y / 2; + } else { + showLabel = false; + } + + return {x: x, + y: y, + showLabel: showLabel}; + }, + + getLabelLocation_old2 = function (locArray, conf) { + var x, y, height = 0, i; + var indexMargin = Math.min(conf.indexMargin, Math.floor((locArray.length - 1) / 2)); + var ratioMargin = 0.15; + var index = indexMargin; + var max = 0; + if (locArray.length >= conf.indexMargin * 2 + 1) { + locArray.forEach(function (d, i, array) { + var m = Math.max(indexMargin, ratioMargin * array.length); + if (i >= m && i <= array.length - m) { + if (d.y > max) { + max = d.y; + index = i; + } + } + }); + x = locArray[index].x; + y = locArray[index].y0 - locArray[index].y / 2; + for (i = index - indexMargin; i <= index + indexMargin; i++) { + height += locArray[i].y; + } + height = height / (2 * indexMargin + 1); + } else { + x = -100; + y = -100; + height = 0; + } + + return { + x: x, + y: y, + height: height + }; + }; + + canvas.rect(0, 0, conf.width, conf.height) + .attr({"stroke": "none", + "fill": "#e0e0e0"}); + for (i = 0, l = this.source.length; i < l; i++) { + _area = area(this.pathSource[i]); + path = canvas.path(_area).attr({fill: color(i), + stroke: color(i), + "stroke-width": 1, + "stroke-linejoin": "round", + "transform": "t0," + conf.topInterval + }); + path.color = color(i); + path.index = i; + path.info = this.infos[i]; + + path.paths = paths; + path.topTrans = conf.topInterval; + path.bottomTrans = conf.bottomInterval; + path.stream = this; + + path.node.streamPath = path; + path.node.setAttribute("class", "streamPath rvml"); + + paths[path.index] = path; + } + + //label + for (i = 0, l = paths.length; i < l; i++) { + path = paths[i]; + path.label = this.canvas.text(0, 0, + conf.pathLabel ? + path.info[0].name + " " + (Math.round(path.info[0].total * 10000) / 100) + "%" : "") + .attr({"text-anchor": "middle", + "fill": "white", + "font-size": conf.fontSize, + "font-family": "微软雅黑"}); + path.labelLoc = getLabelLocation(this.pathSource[i], path.label); + + if (path.labelLoc.showLabel) { + path.label.attr({"x": path.labelLoc.x, + "y": path.labelLoc.y}); + } else { + path.label.attr({"opacity": 0}); + //path.labelOpacity = 1; + } + if (i === 0 && path.info[0].id === -1) { + path.attr({"cursor": "pointer"}); + path.label.attr({"cursor": "pointer"}); + } + labels.push(path.label); + path.label.node.setAttribute("class", "streamPath rvml"); + } + + $(this.canvas.canvas).unbind(); + + var mouseenter = function (e) { + var stream = e.data.stream; + stream.indicatorLine.attr({"stroke": "#000"}); + stream.highlightLine.attr({"stroke": "white"}); + stream.floatTag.css({"visibility" : "visible"}); + stream.axisPopText.show(); + stream.axisPopBubble.show(); + }; + + var mouseleave = function (e) { + var stream = e.data.stream, + circle; + stream.indicatorLine.attr({"stroke": "none"}); + stream.highlightLine.attr({"stroke": "none"}); + stream.floatTag.css({"visibility" : "hidden"}); + stream.axisPopText.hide(); + stream.axisPopBubble.hide(); + //recover prepath; + if (typeof stream.prePath !== 'undefined') { + stream.prePath.attr({"opacity": 1, "stroke-width": 1}); + // set legend + //circle = stream.legends[stream.prePath.index].circle; + //circle.attr({"r": circle.data("r0"), "opacity": 1}); + stream.legends[stream.prePath.index].css({"background": "white"}); + stream.prePath = undefined; + } + }; + + var click = function (e) { + var stream = e.data.stream, + position; + if (typeof stream.prePath !== 'undefined' && stream.prePath.info[0].id === -1) { + //hidden + stream.indicatorLine.attr({"stroke": "none"}); + stream.highlightLine.attr({"stroke": "none"}); + stream.floatTag.css({"visibility" : "hidden"}); + + stream.level += 1; + + //set cover + position = $(this).parent().position(); + stream.cover.css({ + left: position.left, + top: position.top, + visibility: "visible" + }); + stream.coverMouse = {x: e.pageX, y: e.pageY}; + + //redraw + stream.getLevelSource(); + stream.reRender(); + + //hidden + stream.indicatorLine.attr({"stroke": "none"}); + stream.highlightLine.attr({"stroke": "none"}); + stream.floatTag.css({"visibility" : "hidden"}); + + stream.paths.forEach(function (d, i, array) { + d.attr({transform: "s1,0.001,0," + stream.defaults.height}); + d.label.hide(); + d.animate({transform: "t0,0"}, 750, "linear", function () { + stream.cover.style.visibility = "hidden"; + if (typeof stream.coverMouse !== 'undefined') { + stream.indicatorLine.attr({"stroke": "#000"}); + stream.highlightLine.attr({"stroke": "white"}); + stream.floatTag.css({"visibility" : "visible"}); + $(stream.canvas.canvas).trigger("mousemove", + [stream.coverMouse.x, stream.coverMouse.y]); + stream.coverMouse = undefined; + } + //if (d.labelOpacity === 1) + if (d.labelLoc.showLabel) { + d.label.show(); + } + }); + }); + } + }; + + var mousemove = function (e, pageX, pageY) { + var stream = e.data.stream; + var offset = $(this).parent().offset(); + var position = $(this).parent().position(); + //var offset = $(this).offset(); + var x = (e.pageX || pageX) - offset.left, + y = (e.pageY || pageY) - offset.top; + var floatTag, + floatTagWidth, + floatTagHeight, + mouseToFloatTag = {x: 20, y: 20}; + var path, + pathSource = stream.pathSource, + pathSourceP, + pathIndex, + circle; + var i, l; + var xIdx = Math.floor((x / (stream.defaults.width / (stream.source[0].length - 1) / 2) + 1) / 2); + var pathsourceP, + lineX; + + //get path + path = undefined; + pathSource = stream.pathSource; + for (i = 0, l = pathSource.length; i < l; i++) { + if (y >= pathSource[i][xIdx].y0 - pathSource[i][xIdx].y && y <= pathSource[i][xIdx].y0) { + path = stream.paths[i]; + pathIndex = i; + break; + } + } + if (typeof path === 'undefined') { + return; + } + + //recover prepath; + if (typeof stream.prePath !== 'undefined') { + stream.prePath.attr({"opacity": 1, "stroke-width": 1}); + // set legend + stream.legends[stream.prePath.index].css({"background": "white"}); + } + //change new path; + stream.prePath = path; + path.attr({"opacity": 0.5, "stroke-width": 0}); + + // set legend + stream.legends[stream.prePath.index].css({"background": "#dddddd"}); + + //set indicator and highlight line + lineX = stream.defaults.width * xIdx / (stream.source[0].length - 1); + pathSourceP = pathSource[pathSource.length - 1][xIdx]; + stream.indicatorLine.attr({path: "M" + lineX + + " " + (pathSourceP.y0 - pathSourceP.y) + + "V" + pathSource[0][xIdx].y0}); + + pathSourceP = pathSource[pathIndex][xIdx]; + stream.highlightLine.attr({path: "M" + lineX + + " " + (pathSourceP.y0 - pathSourceP.y) + + "V" + pathSourceP.y0}); + if (pathIndex === 0 && path.info[0].id === -1) { + stream.highlightLine.attr({"cursor": "pointer"}); + } else { + stream.highlightLine.attr({"cursor": "auto"}); + } + + floatTag = stream.floatTag; + floatTag.html(path.info[xIdx].tip); + + //axis pop bubble + stream.axisPopText.attr({"text": stream.date[xIdx + stream.timeRange[0]]}) + .transform("t" + (lineX + stream.defaults.margin[3]) + ",0"); + stream.axisPopBubble.transform("t" + (lineX + stream.defaults.margin[3]) + ",0"); + + //customevent; + if (stream.defaults.customEventHandle.mousemove) { + stream.defaults.customEventHandle.mousemove.call(stream, + {"timeIndex": xIdx, "pathIndex": pathIndex}); + } + }; + var $canvas = $(this.canvas.canvas); + $canvas.bind("mouseenter", {"stream": this}, mouseenter); + $canvas.bind("mouseleave", {"stream": this}, mouseleave); + $canvas.bind("click", {"stream": this}, click); + $canvas.bind("mousemove", {"stream": this}, mousemove); + + this.paths = paths; + this.labels = labels; + this.indicatorLine = canvas.path("M0 " + conf.topInterval + "V" + (conf.height - conf.bottomInterval)) + .attr({stroke: "none", "stroke-width": 1, "stroke-dasharray": "- "}); + this.highlightLine = canvas.path("M0 " + conf.topInterval + "V" + (conf.height - conf.bottomInterval)) + .attr({stroke: "none", "stroke-width": 2}); + }; + + /** + * 创建坐标轴 + */ + Stream.prototype.createAxis = function () { + //all date strings' format are same, string length are same + var conf = this.defaults, + date = this.date.slice(this.timeRange[0], this.timeRange[1] + 1), + left = conf.margin[3], + //left = conf.margin[3] + conf.legendWidth, + right = conf.totalWidth - conf.margin[1] - conf.legendWidth, + tempWord, + tickNumber, + getPopPath = function (El) { + //down pop + var x = 0, + y = 0, + size = 4, + cw = 23, + bb = {height: 8}; + if (El) { + bb = El.getBBox(); + bb.height *= 0.6; + cw = bb.width / 2 - size; + } + return [ + 'M', x, y, + 'l', size, size, cw, 0, + 'a', size, size, 0, 0, 1, size, size, + 'l', 0, bb.height, + 'a', size, size, 0, 0, 1, -size, size, + 'l', -(size * 2 + cw * 2), 0, + 'a', size, size, 0, 0, 1, -size, -size, + 'l', 0, -bb.height, + 'a', size, size, 0, 0, 1, size, -size, + 'l', cw, 0, + 'z' + ].join(','); + }; + + this.dateScale = d3.scale.linear() + .domain([0, date.length - 1]) + .range([left, right]); + + tempWord = this.axisPaper.text(0, 0, date[0]); + tickNumber = Math.floor((right - left) / tempWord.getBBox().width / 2) + 1; + tempWord.remove(); + //tickNumber = 4; + + Axis().scale(this.dateScale) + .ticks(tickNumber) + //.ticks(conf.axisTickNumber) + .tickSize(6, 3, 3) + .tickAttr({"stroke": "none"}) + .minorTickAttr({"stroke": "none"}) + .domainAttr({"stroke": "none"}) + //.tickTextAttr({"font-size": conf.fontSize}) + .tickFormat(function (d) { + return date[d] || ""; + })(this.axisPaper); + + this.axisPopText = this.axisPaper.text(0, 11, date[0]) + .attr({ "text-anchor": "middle", + "fill": "#fff", + //"font-size": conf.fontSize, + "transform": "t" + left + ",0"}) + .hide(); + this.axisPopBubble = this.axisPaper.path(getPopPath(this.axisPopText)) + .attr({ "fill": "#000", + //"opacity": 0, + "transform": "t" + left + ",0"}) + .toBack() + .hide(); + }; + + /** + * 获取纵轴最大值 + */ + Stream.prototype.getMaxY = function () { + return d3.max(this.source, function (d) { + return d3.max(d, function (d) { + return d.y0 + d.y; + }); + }); + }; + + /** + * 映射路径源 + */ + Stream.prototype.mapPathSource = function () { + var conf = this.defaults, + maxX = this.source[0].length - 1,//this.digitData[0].length - 1, + maxY = this.getMaxY(), + width = conf.width, + height = conf.height - conf.topInterval - conf.bottomInterval; + var i, j, l, l2, s, ps; + this.pathSource = []; + for (i = 0, l = this.source.length; i < l; i++) { + this.pathSource[i] = []; + for (j = 0, l2 = this.source[0].length; j < l2; j++) { + s = this.source[i][j]; + ps = this.pathSource[i][j] = {}; + ps.x = s.x * width / maxX; + ps.y0 = height - s.y0 * height / maxY; + ps.y = s.y * height / maxY; + } + } + }; + + /** + * 生成区域 + */ + Stream.prototype.generateArea = function () { + this.mapPathSource(); + return d3.svg.area().x(function (d) { + return d.x; + }).y0(function (d) { + return d.y0; + }).y1(function (d) { + return d.y0 - d.y; + }); + }; + + /*! + * 生成区域 + */ + Stream.prototype.generateArea_old = function () { + var conf = this.defaults, + maxX = this.digitData[0].length - 1, + maxY = this.getMaxY(), + width = conf.width, + height = conf.height - conf.topInterval - conf.bottomInterval, + area = d3.svg.area() + .x(function (d) { + return d.x * width / maxX; + }) + .y0(function (d) { + return height - d.y0 * height / maxY; + }) + .y1(function (d) { + return height - (d.y + d.y0) * height / maxY; + }); + return area; + }; + + /** + * 清除画布 + */ + Stream.prototype.clearCanvas = function () { + this.canvas.clear(); + this.legend.empty(); + this.axisPaper.clear(); + }; + + /** + * 重绘图表 + */ + Stream.prototype.reRender = function (options) { + this.setOptions(options); + this.clearCanvas(); + this.layout(); + this.generatePaths(); + this.canAnimate = true; + }; + + /** + * 绘制图表 + */ + Stream.prototype.render = function (options) { + this.firstRender = true; + this.setOptions(options); + this.clearCanvas(); + this.layout(); + this.generatePaths(); + this.firstRender = false; + this.canAnimate = true; + }; + + /** + * 重设图表 + */ + Stream.prototype.resize = function (options) { + var conf = this.defaults; + + if (!options.width && !options.height) { + throw new Error("no width and height input"); + } else if (options.width && !options.height) { + if (conf.autoHeight) { + this.setOptions({"width": options.width}); + } else { + this.setOptions({"width": options.width, "height": conf.height}); + } + } else if (!options.width && options.height) { + this.setOptions({"width": conf.totalWidth, "height": options.height}); + } else { + this.setOptions({"width": options.width, "height": options.height}); + } + + this.node.innerHTML = ""; + this.createCanvas(); + this.reRender(); + }; + + /** + * 侦听自定义事件 + * @param {String} eventName 事件名 + * @param {Function} callback 事件回调函数 + */ + Stream.prototype.on = function (eventName, callback) { + if (typeof this.defaults.customEventHandle[eventName] !== 'undefined') { + this.defaults.customEventHandle[eventName] = callback; + } + }; + + /** + * 设置动画 + * @param {Object} options 选项对象 + * @param {Number} timeDuration 时间 + */ + Stream.prototype.animate = function (options, timeDuration) { + //must after render if new Source has been set; + if (!this.canAnimate) { + throw new Error("Function animate must be called after render if new Source has been set."); + } + var time = 0, + area, + color, + i, + l; + if (arguments.length > 1) { + time = timeDuration; + } + + //this.setOptions(options); + if (options.offset || options.order) { + this.source = this.remapSource(this.digitData); + this.layout(); + } + area = this.generateArea(); + color = this.getColor(); + for (i = 0, l = this.source.length; i < l; i++) { + var _area = area(this.source[i]); + var anim = Raphael.animation({path: _area, fill: color(i)}, time); + this.paths[i].animate(anim); + } + }; + + /*! + * 导出Stream + */ + return Stream; +}); diff --git a/lib/charts/tree.js b/lib/charts/tree.js index 652a106..e2dbaf9 100644 --- a/lib/charts/tree.js +++ b/lib/charts/tree.js @@ -1,580 +1,580 @@ -/*global EventProxy, d3, Raphael, $ */ -/*! - * Tree的兼容性定义 - */ -;(function (name, definition) { - if (typeof define === 'function') { - define(definition); - } else { - this[name] = definition(function (id) { return this[id];}); - } -})('Tree', function (require) { - var DataV = require('DataV'); - var theme = DataV.Themes; - - /** - * Tree的构造函数 - * Examples: - * ``` - * var tree = new Tree("container"); - * tree.setSource(source); - * tree.render(); - * ``` - * Options: - * - `width`: 画布的宽度 - * - `height`: 画布的高度 - */ - var Tree = DataV.extend(DataV.Chart, { - initialize: function (node, options) { - this.type = "Tree"; - this.node = this.checkContainer(node); - - this.addlink = {}; - - // Properties - this.treeDepth = 0; - this.font = {}; - - // Canvas - this.defaults.width = 750; - this.defaults.height = 760; - this.defaults.deep = 180; - this.defaults.radius = 15; - - this.setOptions(options); - this.createCanvas(); - } - }); - - /** - * 饼图纬度描述 - */ - Tree.dimension = {}; - /** - * ID标签 - */ - Tree.dimension.id = { - type: "string", - required: true - }; - /** - * 父ID标签 - */ - Tree.dimension.pid = { - type: "string", - required: true - }; - - Tree.prototype.hierarchyTableToJson = function (table) { - if (table[0][0] === "ID") { - table = table.slice(1); - } - - var rootID; - var hierarchy = {}; - var addlink = {}; //for multi-fathernode - // var ids = _.pluck(table, 0); - // var pids = _.pluck(table, 3); - // var roots = _.difference(pids, ids); - // if (roots.length === 0) { - // throw new Error("root node is empty"); - // } else if (roots.length > 1) { - // throw new Error("root nodes are too many"); - // } - - table.forEach(function (d, i) { - if (d[0] === "") { - throw new Error("ID can not be empty(line:" + (i + 1) + ")."); - } - if (!d[3]) { - if (rootID) { - throw new Error("2 or more lines have an empty parentID(line:" + (i + 1) + ")."); - } else { - rootID = d[0]; - } - } - if (hierarchy[d[0]]) { - throw new Error("2 or more lines have same ID: " + d[0] + "(line:" + (i + 1) + ")."); - } - - var value = ""; - var j, length; - if (d.length > 4) { - for (j = 4, length = d.length; j < length; j++) { - if (j < length - 1) { - value = value + d[j] + ","; - } else { - value = value + d[j]; - } - } - } - hierarchy[d[0]] = {name: d[1], size: d[2], child: [], id: d[0], value: value}; - }); - if (!rootID) { - throw new Error("No root node defined."); - } - table.forEach(function (d, i) { - if (d[3]) { - var record; - var ids = d[3].split(','); - if (ids.length === 1) { - record = hierarchy[d[3]]; - record.child.push(d[0]); - } else { - record = hierarchy[ids[0]]; - record.child.push(d[0]); - addlink[d[0]] = {child: [], path: [], pnode: []}; - - var j, length; - for (j = 1, length = ids.length; j < length; j++) { - addlink[d[0]].child.push(ids[j]); - } - } - if (!record) { - throw new Error("Can not find parent with ID " + d[3] + "(line:" + (i + 1) + ")."); - } - } - }); - - this.addlink = addlink; - - var recurse = function (rootID) { - var record = hierarchy[rootID]; - if (record.child.length === 0) { - if (isNaN(parseFloat(record.size))) { - throw new Error("Leaf node's size is not a number(ID:" + (rootID + 1) + ")."); - } else { - return { - name: record.name, - size: record.size, - num: record.id, - children: null, - draw: false, - value: record.value - }; - } - } else { - var childNode = []; - record.child.forEach(function (d) { - childNode.push(recurse(d)); - }); - return {name: record.name, children: childNode, num: record.id, draw: false, value: record.value}; - } - }; - - return recurse(rootID); - }; - - Tree.prototype.setSource = function (source) { - var conf = this.defaults; - - this.rawData = this.hierarchyTableToJson(source); - this.source = this.remapSource(source); - - this.source.x0 = conf.width / 2; - this.source.y0 = conf.radius * 10; - - this.source.children.forEach(function collapse(d) { - if (d.children) { - // d._children = d.children; - // d._children.forEach(collapse); - // d.children = null; - d._children = null; - d.children.forEach(collapse); - } - }); - }; - - Tree.prototype.remapSource = function (data) { - return this.hierarchyTableToJson(data); - // return data; - }; - - Tree.prototype.layout = function () { - var conf = this.defaults; - var tree = d3.layout.tree() - .size([conf.width, conf.height]); - - this.nodesData = tree.nodes(this.source); - - var treedepth = 0; - var id = 0; - - this.nodesData.forEach(function (d) { - if (d.depth > treedepth) { - treedepth = d.depth; - } - }); - - this.treeDepth = treedepth; - conf.deep = conf.height / (treedepth + 1); - - this.nodesData.forEach(function (d) { - d.y = conf.radius * 3 + d.depth * conf.deep; - d.id = id; - id++; - }); - }; - - Tree.prototype.getColor = function () { - var colorMatrix = DataV.getColor(); - var color; - if (colorMatrix.length > 1 && colorMatrix[0].length > 1) { - color = [colorMatrix[0][0], colorMatrix[1][0]]; - } else { - color = colorMatrix[0]; - } - - return DataV.gradientColor(color, "special"); - }; - - Tree.prototype.createCanvas = function () { - var conf = this.defaults; - this.canvas = new Raphael(this.node, conf.width, conf.height); - this.node.style.position = "relative"; - this.floatTag = DataV.FloatTag()(this.node); - - this.floatTag.css({"visibility": "hidden"}); - - this.DOMNode = $(this.canvas.canvas); - var that = this; - this.DOMNode.click(function (event) { - that.trigger("click", event); - }); - this.DOMNode.dblclick(function (event) { - that.trigger("dblclick", event); - }); - - var mousewheel = document.all ? "mousewheel" : "DOMMouseScroll"; - this.DOMNode.bind(mousewheel, function (event) { - that.trigger("mousewheel", event); - }); - - this.DOMNode.bind("contextmenu", function (event) { - that.trigger("contextmenu", event); - }); - - this.DOMNode.delegate("circle", "click", function (event) { - that.trigger("circle_click", event); - }); - - this.DOMNode.delegate("circle", "mouseover", function (event) { - that.trigger("circle_mouseover", event); - }); - - this.DOMNode.delegate("circle", "mouseout", function (event) { - that.trigger("circle_mouseout", event); - }); - }; - - Tree.prototype.zoom = function (d) { - var multiple = d || 2; - var conf = this.defaults; - conf.width = conf.width * multiple; - - if (conf.height <= this.treeDepth * conf.deep) { - conf.height = conf.height * multiple; - } - - //this.createCanvas(); - this.canvas.setSize(conf.width, conf.height); - this.canvas.setViewBox(0, 0, conf.width, 800); - this.defaults = conf; - - this.render(); - }; - - - Tree.prototype.getLinkPath = function (fx, fy, tx, ty) { - var conf = this.defaults; - - var c1x = fx; - var c1y = fy + (ty - fy) / 2; - var c2x = tx; - var c2y = ty - (ty - fy) / 2; - - var link_path = [["M", fx, fy + conf.radius], - ["C", c1x, c1y, c2x, c2y, tx, ty - conf.radius]]; - - return link_path; - }; - - Tree.prototype.generatePaths = function () { - var canvas = this.canvas; - var source = this.source; - var conf = this.defaults; - var radius = conf.radius; - //canvas.clear(); - var color = this.getColor(); - // var font = this.getFont(); - var font_family = '微软雅黑'; - var font_size = 8; - var treedepth = this.treeDepth; - var nodesData = this.nodesData; - - var n = 0; - - var addlink = this.addlink; - var node; - var num = 0; - - var nodes = canvas.set(); - var path = []; - var textpath = []; - - var tree = this; - var nodeupdate = function () { - tree.update(this.data("num")); - }; - - $(this.node).append(this.floatTag); - - var i, nodesLength; - for (i = 0, nodesLength = nodesData.length; i < nodesLength; i++) { - var d = nodesData[i]; - var parent = d.parent; - - if (addlink[d.num]) { - var j, k, childLength; - for (j = 0, childLength = addlink[d.num].child.length; j < childLength; j++) { - for (k = 0; k < nodesLength; k++) { - if (nodesData[k].num === addlink[d.num].child[j]) { - addlink[d.num].pnode[j] = k; - addlink[d.num].path[j] = canvas.path() - .attr({ stroke: "#939598", "stroke-width": 0.5}); - } - } - } - } - - var startX; - var startY; - - if (parent && d.draw) { - startX = parent.x; - startY = parent.y; - } else { - startX = d.x; - startY = d.y; - } - if (parent) { - path.push(canvas.path().attr({stroke: "#939598", "stroke-width": 0.5})); - } - - nodes.push( - canvas.circle(startX, startY, radius) - .attr({fill: color(d.depth / treedepth), - stroke: "#ffffff", - "stroke-width": 1, - "fill-opacity": 0.4, - "data": 12}) - .data("num", i) - .animate({cx: d.x, cy: d.y}, 500, "backOut") - ); - - if (d.children || d._children) { - nodes[i].click(nodeupdate); - } - - if (d._children) { - nodes[i].attr({ - stroke: color(d.depth / treedepth), - "stroke-width": radius, - "stroke-opacity": 0.4, - "fill-opacity": 1, - "r": radius / 2 - }); - } - - if (d.children) { - textpath.push(canvas.text(d.x, d.y - radius - 7, d.name).attr({'font-size': 12})); - } else { - textpath.push(canvas.text(d.x, d.y + radius + 7, d.name).attr({'font-size': 12})); - } - } - - // var back = function(pid, x, y){ - // s.forEach(function (d, i){ - // if (d.data('pid') == pid){ - // d.animate({cx: x, cy: y}, 200, "backOut"); - // if (nodes[i].children) - // back(d.data('num'), d.attr('cx'), d.attr('cy')); - // } - // }); - // }; - - // s.forEach(function(d, i) { - // d.click(function(){ - // if (nodes[i].children) - // back(d.data('num'), d.attr('cx'), d.attr('cy')); - // tree.update(d.data("num")); - // }); - // }); - var floatTag = this.floatTag; - nodes.forEach(function (d, i) { - $(d.node).attr('value', nodesData[i].value); - var textY = textpath[i].attr('y'); - var thisradius = d.attr('r'); - var thisstrokewidth = d.attr('stroke-width'); - d.mouseover(function () { - if (!nodesData[i]._children) { - this.animate({r: thisradius + 2, "fill-opacity": 0.75}, 100); - } else { - this.animate({r: thisradius + 2, "stroke-opacity": 0.75}, 100); - } - - textpath[i].attr({'font-size': 20}); - - if (i > 0) { - if (!nodesData[i].children) { - textpath[i].animate({'y': textY + 12}, 100, "backOut"); - } else { - textpath[i].animate({'y': textY - 12}, 100, "backOut"); - } - } - - var getFline = function (node, num) { - var parent = node.parent; - if (parent) { - path[node.id - 1].attr({"stroke-width": 4, "stroke-opacity": num}); - if ( num > 0.5) { - num = num - 0.1; - } - getFline(parent, num); - } - }; - - getFline(nodesData[i], 0.9); - - var thisparent = nodesData[i].parent; - var j, textpathLength; - for (j = 0, textpathLength = textpath.length; j < textpathLength; j++) { - var parent = nodesData[j].parent; - if (parent === thisparent && j !== i) { - textpath[j].animate({'fill-opacity': 0.4}); - } - } - - console.log(nodesData[i]); - floatTag.html('
      ' + nodesData[i].name + '
      '); - floatTag.css({"visibility" : "visible"}); - }) - .mouseout(function () { - floatTag.css({"visibility" : "hidden"}); - if (!nodesData[i]._children) { - this.animate({r: thisradius, "fill-opacity": 0.4}, 100); - } else { - this.animate({r: thisradius, "stroke-width": thisstrokewidth, "stroke-opacity": 0.4}, 100); - } - textpath[i].attr({'font-size': 12}); - textpath[i].animate({'y': textY}, 100, "backOut"); - - var getFline = function (node) { - var parent = node.parent; - if (parent) { - path[node.id - 1].attr({"stroke-width": 0.5, "stroke-opacity": 1}); - getFline(parent); - } - }; - getFline(nodesData[i]); - - var thisparent = nodesData[i].parent; - var j, textpathLength; - for (j = 0, textpathLength = textpath.length; j < textpathLength; j++) { - var parent = nodesData[j].parent; - if (parent === thisparent && j !== i) { - textpath[j].animate({'fill-opacity': 1}); - } - } - }); - }); - - nodes.onAnimation(function () { - var pathNum = 0; - var i, nodeslength; - - for (i = 1, nodeslength = nodes.length; i < nodeslength; i++) { - var d = nodes[i]; - var node = nodesData[i]; - var parent = node.parent; - - path[pathNum] - .attr({path: tree.getLinkPath(parent.x, parent.y, d.attr("cx"), d.attr("cy"))}); - - pathNum++; - - if (addlink[node.num]) { - var j, k, linkchildLength, nodesLength; - for (j = 0, linkchildLength = addlink[node.num].child.length; j < linkchildLength; j++) { - for (k = 0, nodesLength = nodesData.length; k < nodesLength; k++) { - var anparent = nodesData[k]; - if (anparent.num === addlink[node.num].child[j]) { - var link_path = tree.getLinkPath(anparent.x, anparent.y, d.attr("cx"), d.attr("cy")); - addlink[node.num].path[j].attr({path: link_path}); - } - } - } - } - } - }); - - this.nodes = nodes; - this.path = path; - this.textpath = textpath; - }; - - Tree.prototype.update = function (i) { - var source = this.source; - var conf = this.defaults; - - source.children.forEach(function clearDraw(d) { - d.draw = false; - if (d.children) { - d.children.forEach(clearDraw); - } - }); - - source.children.forEach(function find(d) { - if (d.id === i) { - if (d.children) { - d._children = d.children; - d.children = null; - } else { - d.children = d._children; - if (d.children) { - d.children.forEach(function drawn(child) { - child.draw = true; - if (child.children) { - child.children.forEach(drawn); - } - }); - } - d._children = null; - } - } else { - if (d.children) { - d.children.forEach(find); - } - } - }); - this.source = source; - this.source.x0 = conf.width / 2; - this.source.y0 = conf.radius * 2; - this.render(); - }; - - /** - * 渲染Tree - */ - Tree.prototype.render = function (options) { - this.canvas.clear(); - this.setOptions(options); - this.layout(); - // var st2 = new Date().getTime(); - this.generatePaths(); - // var et = new Date().getTime(); - //this.canvas.renderfix(); - }; - - return Tree; -}); +/*global EventProxy, d3, Raphael, $ */ +/*! + * Tree的兼容性定义 + */ +;(function (name, definition) { + if (typeof define === 'function') { + define(definition); + } else { + this[name] = definition(function (id) { return this[id];}); + } +})('Tree', function (require) { + var DataV = require('DataV'); + var theme = DataV.Themes; + + /** + * Tree的构造函数 + * Examples: + * ``` + * var tree = new Tree("container"); + * tree.setSource(source); + * tree.render(); + * ``` + * Options: + * - `width`: 画布的宽度 + * - `height`: 画布的高度 + */ + var Tree = DataV.extend(DataV.Chart, { + initialize: function (node, options) { + this.type = "Tree"; + this.node = this.checkContainer(node); + + this.addlink = {}; + + // Properties + this.treeDepth = 0; + this.font = {}; + + // Canvas + this.defaults.width = 750; + this.defaults.height = 760; + this.defaults.deep = 180; + this.defaults.radius = 15; + + this.setOptions(options); + this.createCanvas(); + } + }); + + /** + * 饼图纬度描述 + */ + Tree.dimension = {}; + /** + * ID标签 + */ + Tree.dimension.id = { + type: "string", + required: true + }; + /** + * 父ID标签 + */ + Tree.dimension.pid = { + type: "string", + required: true + }; + + Tree.prototype.hierarchyTableToJson = function (table) { + if (table[0][0] === "ID") { + table = table.slice(1); + } + + var rootID; + var hierarchy = {}; + var addlink = {}; //for multi-fathernode + // var ids = _.pluck(table, 0); + // var pids = _.pluck(table, 3); + // var roots = _.difference(pids, ids); + // if (roots.length === 0) { + // throw new Error("root node is empty"); + // } else if (roots.length > 1) { + // throw new Error("root nodes are too many"); + // } + + table.forEach(function (d, i) { + if (d[0] === "") { + throw new Error("ID can not be empty(line:" + (i + 1) + ")."); + } + if (!d[3]) { + if (rootID) { + throw new Error("2 or more lines have an empty parentID(line:" + (i + 1) + ")."); + } else { + rootID = d[0]; + } + } + if (hierarchy[d[0]]) { + throw new Error("2 or more lines have same ID: " + d[0] + "(line:" + (i + 1) + ")."); + } + + var value = ""; + var j, length; + if (d.length > 4) { + for (j = 4, length = d.length; j < length; j++) { + if (j < length - 1) { + value = value + d[j] + ","; + } else { + value = value + d[j]; + } + } + } + hierarchy[d[0]] = {name: d[1], size: d[2], child: [], id: d[0], value: value}; + }); + if (!rootID) { + throw new Error("No root node defined."); + } + table.forEach(function (d, i) { + if (d[3]) { + var record; + var ids = d[3].split(','); + if (ids.length === 1) { + record = hierarchy[d[3]]; + record.child.push(d[0]); + } else { + record = hierarchy[ids[0]]; + record.child.push(d[0]); + addlink[d[0]] = {child: [], path: [], pnode: []}; + + var j, length; + for (j = 1, length = ids.length; j < length; j++) { + addlink[d[0]].child.push(ids[j]); + } + } + if (!record) { + throw new Error("Can not find parent with ID " + d[3] + "(line:" + (i + 1) + ")."); + } + } + }); + + this.addlink = addlink; + + var recurse = function (rootID) { + var record = hierarchy[rootID]; + if (record.child.length === 0) { + if (isNaN(parseFloat(record.size))) { + throw new Error("Leaf node's size is not a number(ID:" + (rootID + 1) + ")."); + } else { + return { + name: record.name, + size: record.size, + num: record.id, + children: null, + draw: false, + value: record.value + }; + } + } else { + var childNode = []; + record.child.forEach(function (d) { + childNode.push(recurse(d)); + }); + return {name: record.name, children: childNode, num: record.id, draw: false, value: record.value}; + } + }; + + return recurse(rootID); + }; + + Tree.prototype.setSource = function (source) { + var conf = this.defaults; + + this.rawData = this.hierarchyTableToJson(source); + this.source = this.remapSource(source); + + this.source.x0 = conf.width / 2; + this.source.y0 = conf.radius * 10; + + this.source.children.forEach(function collapse(d) { + if (d.children) { + // d._children = d.children; + // d._children.forEach(collapse); + // d.children = null; + d._children = null; + d.children.forEach(collapse); + } + }); + }; + + Tree.prototype.remapSource = function (data) { + return this.hierarchyTableToJson(data); + // return data; + }; + + Tree.prototype.layout = function () { + var conf = this.defaults; + var tree = d3.layout.tree() + .size([conf.width, conf.height]); + + this.nodesData = tree.nodes(this.source); + + var treedepth = 0; + var id = 0; + + this.nodesData.forEach(function (d) { + if (d.depth > treedepth) { + treedepth = d.depth; + } + }); + + this.treeDepth = treedepth; + conf.deep = conf.height / (treedepth + 1); + + this.nodesData.forEach(function (d) { + d.y = conf.radius * 3 + d.depth * conf.deep; + d.id = id; + id++; + }); + }; + + Tree.prototype.getColor = function () { + var colorMatrix = DataV.getColor(); + var color; + if (colorMatrix.length > 1 && colorMatrix[0].length > 1) { + color = [colorMatrix[0][0], colorMatrix[1][0]]; + } else { + color = colorMatrix[0]; + } + + return DataV.gradientColor(color, "special"); + }; + + Tree.prototype.createCanvas = function () { + var conf = this.defaults; + this.canvas = new Raphael(this.node, conf.width, conf.height); + this.node.style.position = "relative"; + this.floatTag = DataV.FloatTag()(this.node); + + this.floatTag.css({"visibility": "hidden"}); + + this.DOMNode = $(this.canvas.canvas); + var that = this; + this.DOMNode.click(function (event) { + that.trigger("click", event); + }); + this.DOMNode.dblclick(function (event) { + that.trigger("dblclick", event); + }); + + var mousewheel = document.all ? "mousewheel" : "DOMMouseScroll"; + this.DOMNode.bind(mousewheel, function (event) { + that.trigger("mousewheel", event); + }); + + this.DOMNode.bind("contextmenu", function (event) { + that.trigger("contextmenu", event); + }); + + this.DOMNode.delegate("circle", "click", function (event) { + that.trigger("circle_click", event); + }); + + this.DOMNode.delegate("circle", "mouseover", function (event) { + that.trigger("circle_mouseover", event); + }); + + this.DOMNode.delegate("circle", "mouseout", function (event) { + that.trigger("circle_mouseout", event); + }); + }; + + Tree.prototype.zoom = function (d) { + var multiple = d || 2; + var conf = this.defaults; + conf.width = conf.width * multiple; + + if (conf.height <= this.treeDepth * conf.deep) { + conf.height = conf.height * multiple; + } + + //this.createCanvas(); + this.canvas.setSize(conf.width, conf.height); + this.canvas.setViewBox(0, 0, conf.width, 800); + this.defaults = conf; + + this.render(); + }; + + + Tree.prototype.getLinkPath = function (fx, fy, tx, ty) { + var conf = this.defaults; + + var c1x = fx; + var c1y = fy + (ty - fy) / 2; + var c2x = tx; + var c2y = ty - (ty - fy) / 2; + + var link_path = [["M", fx, fy + conf.radius], + ["C", c1x, c1y, c2x, c2y, tx, ty - conf.radius]]; + + return link_path; + }; + + Tree.prototype.generatePaths = function () { + var canvas = this.canvas; + var source = this.source; + var conf = this.defaults; + var radius = conf.radius; + //canvas.clear(); + var color = this.getColor(); + // var font = this.getFont(); + var font_family = '微软雅黑'; + var font_size = 8; + var treedepth = this.treeDepth; + var nodesData = this.nodesData; + + var n = 0; + + var addlink = this.addlink; + var node; + var num = 0; + + var nodes = canvas.set(); + var path = []; + var textpath = []; + + var tree = this; + var nodeupdate = function () { + tree.update(this.data("num")); + }; + + $(this.node).append(this.floatTag); + + var i, nodesLength; + for (i = 0, nodesLength = nodesData.length; i < nodesLength; i++) { + var d = nodesData[i]; + var parent = d.parent; + + if (addlink[d.num]) { + var j, k, childLength; + for (j = 0, childLength = addlink[d.num].child.length; j < childLength; j++) { + for (k = 0; k < nodesLength; k++) { + if (nodesData[k].num === addlink[d.num].child[j]) { + addlink[d.num].pnode[j] = k; + addlink[d.num].path[j] = canvas.path() + .attr({ stroke: "#939598", "stroke-width": 0.5}); + } + } + } + } + + var startX; + var startY; + + if (parent && d.draw) { + startX = parent.x; + startY = parent.y; + } else { + startX = d.x; + startY = d.y; + } + if (parent) { + path.push(canvas.path().attr({stroke: "#939598", "stroke-width": 0.5})); + } + + nodes.push( + canvas.circle(startX, startY, radius) + .attr({fill: color(d.depth / treedepth), + stroke: "#ffffff", + "stroke-width": 1, + "fill-opacity": 0.4, + "data": 12}) + .data("num", i) + .animate({cx: d.x, cy: d.y}, 500, "backOut") + ); + + if (d.children || d._children) { + nodes[i].click(nodeupdate); + } + + if (d._children) { + nodes[i].attr({ + stroke: color(d.depth / treedepth), + "stroke-width": radius, + "stroke-opacity": 0.4, + "fill-opacity": 1, + "r": radius / 2 + }); + } + + if (d.children) { + textpath.push(canvas.text(d.x, d.y - radius - 7, d.name).attr({'font-size': 12})); + } else { + textpath.push(canvas.text(d.x, d.y + radius + 7, d.name).attr({'font-size': 12})); + } + } + + // var back = function(pid, x, y){ + // s.forEach(function (d, i){ + // if (d.data('pid') == pid){ + // d.animate({cx: x, cy: y}, 200, "backOut"); + // if (nodes[i].children) + // back(d.data('num'), d.attr('cx'), d.attr('cy')); + // } + // }); + // }; + + // s.forEach(function(d, i) { + // d.click(function(){ + // if (nodes[i].children) + // back(d.data('num'), d.attr('cx'), d.attr('cy')); + // tree.update(d.data("num")); + // }); + // }); + var floatTag = this.floatTag; + nodes.forEach(function (d, i) { + $(d.node).attr('value', nodesData[i].value); + var textY = textpath[i].attr('y'); + var thisradius = d.attr('r'); + var thisstrokewidth = d.attr('stroke-width'); + d.mouseover(function () { + if (!nodesData[i]._children) { + this.animate({r: thisradius + 2, "fill-opacity": 0.75}, 100); + } else { + this.animate({r: thisradius + 2, "stroke-opacity": 0.75}, 100); + } + + textpath[i].attr({'font-size': 20}); + + if (i > 0) { + if (!nodesData[i].children) { + textpath[i].animate({'y': textY + 12}, 100, "backOut"); + } else { + textpath[i].animate({'y': textY - 12}, 100, "backOut"); + } + } + + var getFline = function (node, num) { + var parent = node.parent; + if (parent) { + path[node.id - 1].attr({"stroke-width": 4, "stroke-opacity": num}); + if ( num > 0.5) { + num = num - 0.1; + } + getFline(parent, num); + } + }; + + getFline(nodesData[i], 0.9); + + var thisparent = nodesData[i].parent; + var j, textpathLength; + for (j = 0, textpathLength = textpath.length; j < textpathLength; j++) { + var parent = nodesData[j].parent; + if (parent === thisparent && j !== i) { + textpath[j].animate({'fill-opacity': 0.4}); + } + } + + console.log(nodesData[i]); + floatTag.html('
      ' + nodesData[i].name + '
      '); + floatTag.css({"visibility" : "visible"}); + }) + .mouseout(function () { + floatTag.css({"visibility" : "hidden"}); + if (!nodesData[i]._children) { + this.animate({r: thisradius, "fill-opacity": 0.4}, 100); + } else { + this.animate({r: thisradius, "stroke-width": thisstrokewidth, "stroke-opacity": 0.4}, 100); + } + textpath[i].attr({'font-size': 12}); + textpath[i].animate({'y': textY}, 100, "backOut"); + + var getFline = function (node) { + var parent = node.parent; + if (parent) { + path[node.id - 1].attr({"stroke-width": 0.5, "stroke-opacity": 1}); + getFline(parent); + } + }; + getFline(nodesData[i]); + + var thisparent = nodesData[i].parent; + var j, textpathLength; + for (j = 0, textpathLength = textpath.length; j < textpathLength; j++) { + var parent = nodesData[j].parent; + if (parent === thisparent && j !== i) { + textpath[j].animate({'fill-opacity': 1}); + } + } + }); + }); + + nodes.onAnimation(function () { + var pathNum = 0; + var i, nodeslength; + + for (i = 1, nodeslength = nodes.length; i < nodeslength; i++) { + var d = nodes[i]; + var node = nodesData[i]; + var parent = node.parent; + + path[pathNum] + .attr({path: tree.getLinkPath(parent.x, parent.y, d.attr("cx"), d.attr("cy"))}); + + pathNum++; + + if (addlink[node.num]) { + var j, k, linkchildLength, nodesLength; + for (j = 0, linkchildLength = addlink[node.num].child.length; j < linkchildLength; j++) { + for (k = 0, nodesLength = nodesData.length; k < nodesLength; k++) { + var anparent = nodesData[k]; + if (anparent.num === addlink[node.num].child[j]) { + var link_path = tree.getLinkPath(anparent.x, anparent.y, d.attr("cx"), d.attr("cy")); + addlink[node.num].path[j].attr({path: link_path}); + } + } + } + } + } + }); + + this.nodes = nodes; + this.path = path; + this.textpath = textpath; + }; + + Tree.prototype.update = function (i) { + var source = this.source; + var conf = this.defaults; + + source.children.forEach(function clearDraw(d) { + d.draw = false; + if (d.children) { + d.children.forEach(clearDraw); + } + }); + + source.children.forEach(function find(d) { + if (d.id === i) { + if (d.children) { + d._children = d.children; + d.children = null; + } else { + d.children = d._children; + if (d.children) { + d.children.forEach(function drawn(child) { + child.draw = true; + if (child.children) { + child.children.forEach(drawn); + } + }); + } + d._children = null; + } + } else { + if (d.children) { + d.children.forEach(find); + } + } + }); + this.source = source; + this.source.x0 = conf.width / 2; + this.source.y0 = conf.radius * 2; + this.render(); + }; + + /** + * 渲染Tree + */ + Tree.prototype.render = function (options) { + this.canvas.clear(); + this.setOptions(options); + this.layout(); + // var st2 = new Date().getTime(); + this.generatePaths(); + // var et = new Date().getTime(); + //this.canvas.renderfix(); + }; + + return Tree; +}); diff --git a/lib/charts/treemap.js b/lib/charts/treemap.js index 07dd001..dbccc6a 100644 --- a/lib/charts/treemap.js +++ b/lib/charts/treemap.js @@ -1,740 +1,740 @@ -/*global d3, $, define */ -/*! - * Treemap兼容定义部分 - */ -;(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];}); - } -})('Treemap', function (require) { - var DataV = require('DataV'); - - /* - * Treemap构造函数,继承自Chart - * Options: - * - * - `width` 数字,图片宽度,默认为750,表示图片高750px - * - `height` 数字,图片高度,默认为500 - * - `showBackTag` 布尔值,回退操作导航条是否显示,默认为 true, 显示;设为false则不显示 - * - `backHeight` 数字,回退操作导航条宽度,默认为20 - * - `level1BorderWidth` 数字,一级方框的边框宽度,默认为1(1px),不建议修改 - * - `level2BorderWidth` 数字,二级方框的边框宽度,默认为1(1px),不建议修改 - * - `fontSizeRatio` 数字,表示图中文字大小。默认为1.0(1倍), 若设为2.0,字体大小会加倍; - * - `customEvent` 函数对象,其中有4个自定义函数。`leafNodeClick` 函数,表示点击叶子节点的事件响应,默认为空函数; `hoverIn` 函数,表示鼠标移进方框的事件响应,默认为空函数; `hoverOut` 函数,表示鼠标移出方框的事件响应,默认为空函数; `mouseover` 函数,表示在方框内移动鼠标的事件响应,默认为设置浮框的内容,可以替换它修改浮框内容; 这些函数可以在创建对象或setOption()时一起设置,也可以通过on函数单独设置。 - * - * Examples: - * create treemap in a dom node with id "chart", width is 500; height is 600px; - * ``` - * var treemap = new Treemap("chart", {"width": 500, "height": 600}); - * ``` - * @param {Object} node The dom node or dom node Id - * @param {Object} options JSON object for determin treemap style - */ - var Treemap = DataV.extend(DataV.Chart, { - initialize: function (node, options) { - this.type = "Treemap"; - this.node = this.checkContainer(node); - - // Properties - this.selectedTreeNodes = [];//array of nodes on the path from root to recent node - this.treeNodeJson = {}; - this.level_ = 2; - - this.floatTag;//浮框对象,这是个可操作的对象。 - - // Canvas - this.defaults.width = 750; - this.defaults.height = 500; - - this.defaults.showBackTag = true; - this.defaults.backHeight = 20; - - this.defaults.level1BorderWidth = 1; - this.defaults.level2BorderWidth = 1; - this.defaults.fontSizeRatio = 1.0; - - //event - this.defaults.customEvent = { - leafNodeClick : function () {}, - hoverIn : function () {}, - hoverOut : function () {}, - mousemove : function () { - var jqNode = this.jqNode, - treemap = jqNode.treemap, - floatTag = treemap.floatTag; - - //set floatTag content - floatTag.html('
      ' + jqNode.treemapNode.name + '
      ' + - '
      ' + jqNode.treemapNode.value + '
      '); - } - }; - - this.setOptions(options); - this.createCanvas(); - } - }); - - /** - * Create dom node relate to treemap - */ - Treemap.prototype.createCanvas = function () { - var conf = this.defaults, - floatStyle, - container = this.node, - backStyle, - canvasStyle; - - this.node.style.position = "relative"; - - if (conf.showBackTag) { - this.backTag = document.createElement("div"); - backStyle = this.backTag.style; - backStyle.width = conf.width + "px"; - backStyle.height = conf.backHeight + "px"; - backStyle.paddingLeft = "5px"; - container.appendChild(this.backTag); - } - - this.canvas = document.createElement("div"); - canvasStyle = this.canvas.style; - canvasStyle.position = "relative"; - canvasStyle.width = conf.width + "px"; - canvasStyle.height = conf.height + "px"; - container.appendChild(this.canvas); - - this.floatTag = DataV.FloatTag()(this.canvas); - - this.floatTag.css({"visibility": "hidden"}); - - //this.canvas.appendChild(this.floatTag); - }; - - /*! - * Get color function according to level 1 node name - * 根据一级节点名获取颜色函数 - */ - Treemap.prototype._changeLevel1NodeColorIndex = function (nodeName) { - if (!this._getNodeTheme) { - this._getNodeTheme = d3.scale.ordinal().range(d3.range(DataV.getColor().length)); - } - return this._getNodeTheme(nodeName); - }; - - /** - * 获取颜色 - * Examples: - * ``` - * // 获取第二种颜色的渐变色。 - * {mode: "gradient", index: 1} - * // 获取最深的离散色。 - * {mode: "random", ratio: 0} - * // 获取最浅的离散色。 - * {mode: "random", ratio: 1} - * // 获取适中的离散色。 - * {mode: "random", ratio: 0.5} - * ``` - * @param {Object} colorJson Way to get color from color theme matrix - * @return {Array} 返回颜色数组 - */ - Treemap.prototype.getColor = function (colorJson) { - var colorMatrix = DataV.getColor(); - var color; - var colorStyle = colorJson || {}; - var colorMode = colorStyle.mode || 'default'; - var i, l; - - switch (colorMode) { - case "multiColorGradient": - //color = d3.interpolateHsl.apply(null, ["red", "blue"]); - //color = d3.interpolateHsl.apply(null, [colorMatrix[0][0], colorMatrix[colorMatrix.length - 1][0]]); - //color = DataV.gradientColor(["#f5f5f6", "#f6f5f5"], 'special'); - //color = DataV.gradientColor([colorMatrix[0][0], colorMatrix[colorMatrix.length - 1][0]], 'special'); - //color = d3.interpolateRgb.apply(null, [colorMatrix[0][0], colorMatrix[colorMatrix.length - 1][0]]); - - color = (function () { - var c = []; - colorMatrix.forEach(function (d, i) { - c.push(d[0]); - }); - return function (ratio) { - var index = (c.length - 1) * ratio; - var floor = Math.floor(index); - var ceil = Math.ceil(index); - if (floor === ceil) { - return c[floor]; - } else { - return d3.interpolateRgb.apply(null, [c[floor], c[ceil]])(index - floor); - } - }; - }()); - //color = d3.interpolateRgb.apply(null, ["green", "purple"]); - break; - 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; - }; - - /* - * 设置数据源 - * Examples: - * treemap数据输入的格式可以是二维数组。例如下面的数组表示2000年4个季度的天数。 - * 第1季度下面还列出了1-3月的天数。数组的第一行为四个固定的字符串"ID","name","size"和"parentID"。 - * 四列数据分别表示层次数据集中各结点的ID,名称,大小和父节点ID。叶子节点必须有大小,根结点不能有父节点ID。各结点的ID、名称必须要有。 - * ``` - * [ - * ["ID", "name", "size", "parentID"], - * [0, "2000", , ], - * [1, "season1", , 0], - * [2, "January", 31, 1], - * [3, "February", 29, 1], - * [4, "Match", 31, 1], - * [5, "season2", 91, 0], - * [6, "season3", 92, 0], - * [7, "season4", 92, 0] - * ] - * ``` - * 数据还可以是json格式。每个结点都有`name`,如果是父节点则还有`children`,如果为叶节点则还有`size`。以上数组数据对应的json数据如下: - * ``` - * { - * "name": "2000", - * "children": [ - * { - * "name": "season1", - * "children": [ - * {"name": "January", "size": 31}, - * {"name": "February", "size": 29}, - * {"name": "Match", "size": 31} - * ] - * }, - * {"name": "season2", "size": 91}, - * {"name": "season3", "size": 92}, - * {"name": "season4", "size": 92}, - * ] - * } - * ``` - * @param {Array|Object} source json or 2-d array - */ - Treemap.prototype.setSource = function (source) { - if (source instanceof Array) { - this.rawData = this._arrayToJson(source); - } else { - this.rawData = source; - } - this.source = this._remapSource(this.rawData); - this.selectedTreeNodes = [this.source[0]]; - }; - - /*! - * Change 2-d array source data to json format. - * @param {Array} array 待转换的二维数组 - * @return {Object} 返回转换后的对象 - */ - Treemap.prototype._arrayToJson = function (array) { - // ID name size parentID - var getColumnIndex = function (columnName) { - var title = array[0]; - var i, l; - for (i = 0, l = title.length; i < l; i++) { - if (title[i] === columnName) { - return i; - } - } - return -1; - }; - - var idx = {}; - var column = ["ID", "name", "size", "parentID"]; - var i, l; - for (i = 0, l = column.length; i < l; i++) { - if ((idx[column[i]] = getColumnIndex(column[i])) === -1) { - throw new Error("no column \'" + column[i] + "\'"); - } - } - - var table = []; - for (i = 1, l = array.length; i < l; i++) { - var line = array[i]; - var tableLine = table[i - 1] = []; - var j, columnL; - for (j = 0, columnL = column.length; j < columnL; j++) { - tableLine.push(line[idx[column[j]]]); - } - } - - var rootID; - var h = {}; - table.forEach(function (d, i) { - if (d[0] === "") { - throw new Error("ID can not be empty(line:" + (i + 1) + ")."); - } - if (!d[3]) { - if (rootID) { - throw new Error("2 or more lines have an empty parentID(line:" + (i + 1) + ")."); - } else { - rootID = d[0]; - } - } - if (h[d[0]]) { - throw new Error("2 or more lines have same ID: " + d[0] + "(line:" + (i + 1) + ")."); - } - h[d[0]] = {name: d[1], size: d[2], child: []}; - }); - if (!rootID) { - throw new Error("No root node defined."); - } - table.forEach(function (d, i) { - if (d[3]) { - var record = h[d[3]]; - if (!record) { - throw new Error("Can not find parent with ID " + d[3] + "(line:" + (i + 1) + ")."); - } - record.child.push(d[0]); - } - }); - var recurse = function (parentID) { - var record = h[parentID]; - if (record.child.length === 0) { - if (isNaN(parseFloat(record.size))) { - throw new Error("Leaf node's size is not a number(name:" + record.name + ")."); - } else { - return {name: record.name, size: record.size}; - } - } else { - var childNode = []; - record.child.forEach(function (d) { - childNode.push(recurse(d)); - }); - return {name: record.name, children: childNode}; - } - }; - return recurse(rootID); - }; - - /*! - * map digit data to layout data format. - * @param source The digit data source from source. - */ - Treemap.prototype._remapSource = function (data) { - var conf = this.defaults; - - var treemap = this._createTreemap(); - var source = treemap.nodes(data); - - var recurse = function (node) { - if (!node.children) { - return node.size; - } - var size = 0; - node.children.forEach(function (d) { - size += parseFloat(recurse(d), 10); - }); - node.size = size; - return node.size; - }; - recurse(source[0]); - - return source; - }; - - /*! - * input a node, return a json tree contains the node's level 1 children and level 2 children; - */ - Treemap.prototype._create2LevelJson = function (node) { - // return json - var recurse = function (node, depth) { - if (depth === 2 && node.refer) { - node = node.refer; - } - if ((!node.children) || depth <= 0) { - return {name: node.name, size: node.size, refer: node}; - } - var childNode = []; - node.children.forEach(function (d) { - childNode.push(recurse(d, depth - 1)); - }); - return {name: node.name, children: childNode, refer: node}; - }; - - this.treeNodeJson = recurse(node, this.level_); - }; - - /*! - * add the clicked leaf to selected tree nodes. - */ - Treemap.prototype._goToLeaf = function (treemapNode) { - this.selectedTreeNodes.push(treemapNode); - this.reRender(); - }; - - /*! - * remove recent leaf node, set leaf node's parent node as new leaf node; - */ - Treemap.prototype._goToRoot = function (idx) { - this.selectedTreeNodes = this.selectedTreeNodes.slice(0, idx + 1); - this.reRender(); - }; - - /*! - * create treemap layout function - */ - Treemap.prototype._createTreemap = function () { - var conf = this.defaults; - return d3.layout.treemap() - .size([conf.width, conf.height]) - .value(function (d) { return d.size; }); - }; - - /*! - * d3 treemap layout - */ - Treemap.prototype.layout = function () { - var treemap = this._createTreemap() - .sort(function (a, b) { return a.value - b.value; }); - this.nodes = treemap.nodes(this.treeNodeJson); - }; - - /*! - * 生成绘制路径 - */ - Treemap.prototype.generatePaths = function () { - //add interactive need - var canvas = this.canvas, - conf = this.defaults, - color, - leafIndex = 0, - leafCount, - colorRatio, - level1Node, - level1NodeArray = [], - funcArray = {}, - i, - l, - level1Count = 0, - level1ColorFun, - goIn = function (event) { - var treemap = event.data.treemap, - treemapNode = event.data.treemapNode, - jqueryNode = event.data.jqueryNode; - jqueryNode.css({'z-index': 30, - "backgroundColor": jqueryNode.color}) - .animate({ - left : 0, - top : 0, - width : treemap.defaults.width, - height : treemap.defaults.height - }, 1000, function () { - treemap._goToLeaf(treemapNode); - }); - }, - - goOut = function (event) { - var treemap = event.data.treemap; - if (treemap.selectedTreeNodes.length > 1) { - treemap._goToRoot(treemap.selectedTreeNodes.length - 2); - } - return false; - }, - - level1HoverIn = function (index, level1NodeArray) { - return function () { - level1NodeArray.forEach(function (d, i, Array) { - if (i === index) { - d.css("font-size", Array[i].fontSizeValue * 6 / 5 + "px"); - if (d.treemapNode.children) { - Array[i].css("backgroundColor", ''); - } - } else { - Array[i].css("backgroundColor", - d3.interpolateRgb.apply(null, [Array[i].color, "#fff"])(0.4)); - } - }); - level1NodeArray[0].treemap.floatTag.css({"visibility" : "visible"}); - }; - }, - - level1HoverOut = function (level1NodeArray) { - return function () { - level1NodeArray.forEach(function (d, i, Array) { - Array[i].css("backgroundColor", Array[i].color) - .css("font-size", Array[i].fontSizeValue + "px"); - }); - level1NodeArray[0].treemap.floatTag.css({"visibility" : "hidden"}); - }; - }, - - level0HoverIn = function () { - this.jqNode.treemap.floatTag.css({"visibility" : "visible"}); - }, - - level0HoverOut = function () { - this.jqNode.treemap.floatTag.css({"visibility" : "hidden"}); - }, - - customEvent = function (f, o) { - return function () { - f.call(o); - }; - }; - - $(canvas).append(this.floatTag);//canvas clear before draw, add floatTag. - - for (i = 0, l = this.nodes.length; i < l; i++) { - var d = this.nodes[i],//treemap node - borderWidth, - w, - h, - left, - top, - depthColor, - depthColorHsl, - jqNode = $(document.createElement("div")); - - if (!(d.parent && d.parent.parent)) { - //level 0 and 1 - color = this.getColor({mode: "gradient", index: this._changeLevel1NodeColorIndex(d.name)}); - leafIndex = -1; - leafCount = d.children ? d.children.length : 0; - colorRatio = 0.5; - - if (d.parent) { - //only level 1 - level1NodeArray.push(jqNode); - level1Node = jqNode; - } - jqNode.color = color(colorRatio); - if (d.parent) { - //level 1 - if (this.selectedTreeNodes.length === 1) { - //root - if (!level1ColorFun) { - level1ColorFun = this.getColor({mode: "multiColorGradient"}); - } - } else { - if (!level1ColorFun) { - depthColor = this.selectedTreeNodes[this.selectedTreeNodes.length - 1].color; - depthColorHsl = d3.hsl(depthColor); - level1ColorFun = d3.interpolateHsl.apply(null, [ - d3.hsl((depthColorHsl.h + 180) % 360, depthColorHsl.s, depthColorHsl.l).toString(), - depthColor - ]); - } - } - jqNode.color = d.parent.children.length === 1 - ? level1ColorFun(0) - : level1ColorFun((level1Count++) / (d.parent.children.length - 1)); - color = d3.interpolateRgb.apply(null, [jqNode.color, - d3.interpolateRgb.apply(null, [jqNode.color, "#fff"])(0.5)]); - } else { - //level 0 - if (this.selectedTreeNodes.length > 1) { - jqNode.color = this.selectedTreeNodes[this.selectedTreeNodes.length - 1].color; - } - } - } else { - //level 2 - leafIndex += 1; - colorRatio = leafCount === 1 ? 0 : leafIndex / (leafCount - 1); - jqNode.color = color(colorRatio); - } - jqNode.treemapNode = d; - d.color = jqNode.color; - jqNode.treemap = this; - jqNode[0].jqNode = jqNode; - jqNode.appendTo($(canvas)); - - jqNode.css("borderStyle", "solid") - .css("borderColor", "#d6d6d6") - .css("overflow", "hidden") - .css("fontFamily", '黑体') - //.css("fontFamily", 'sans-serif') - .css("position", "absolute") - .css("textIndent", "2px") - .css("backgroundColor", jqNode.color) - //.css("color", "#fff"); - .css("color", "white"); - //.css("textAlign", "center"); - - if (!d.parent) { - // level 0 node - if (!d.children) { - jqNode.html(d.name); - } - } else if (d.parent && !d.parent.parent) { - // level 1 node - jqNode.html(d.name) - .css("zIndex", 20); - if (d.children) { - //jqNode.css("backgroundColor", ''); - jqNode.css("backgroundColor", jqNode.color); - } - } else { - // level 2 node - jqNode.css("borderColor", "rgba(255, 255, 255, 0.5)"); - } - - // position - // border do not need adjust, still 1 px; - borderWidth = 1; - w = Math.max(0, d.dx - borderWidth); - h = Math.max(0, d.dy - borderWidth); - jqNode.fontSizeValue = Math.max(conf.fontSizeRatio - * parseInt(60 * Math.sqrt(w * h / conf.width / conf.height), 10), - 12); - jqNode.css("borderWidth", borderWidth + "px") - .css("left", d.x + "px") - .css("top", d.y + "px") - .css("width", w + "px") - .css("height", h + "px") - .css("fontSize", jqNode.fontSizeValue + "px"); - //.css("lineHeight", Math.max(0, d.dy - borderWidth ) + "px"); - - if (d.parent && !d.parent.parent) { - //level 1 - jqNode.css("cursor", "pointer"); - jqNode.clone().insertBefore(jqNode).css("opacity", 0.01); - jqNode.click({"treemap": this, "treemapNode": d, "jqueryNode": jqNode}, goIn); - jqNode.mouseover(level1HoverIn(level1NodeArray.length - 1, level1NodeArray)); - jqNode.mouseout(level1HoverOut(level1NodeArray)); - jqNode.mousemove(this.defaults.customEvent.mousemove); - } - - if (!d.parent) { - //level 0; leaf node - jqNode.mouseover(level0HoverIn); - jqNode.mouseout(level0HoverOut); - jqNode.mousemove(this.defaults.customEvent.mousemove); - } - - jqNode.bind("contextmenu", {"treemap": this}, goOut); - - // custom interacitves - var o = {treemapNode : d, - node : jqNode, - name : d.name, - parent : d.parent, - value : d.value - }; - - if (d.parent && (!d.parent.parent) && (!d.children)) { - jqNode.click(customEvent(this.defaults.customEvent.leafNodeClick, o)); - } - - jqNode.mouseover(customEvent(this.defaults.customEvent.hoverIn, o)); - - jqNode.mouseout(customEvent(this.defaults.customEvent.hoverOut, o)); - - //canvas.appendChild(jqNode[0]); - } - }; - - /** - * 清除画布 - */ - Treemap.prototype.clearCanvas = function () { - var canvas = this.canvas; - canvas.innerHTML = ""; - }; - - /*! - * set back navi link - */ - Treemap.prototype._setBackTag = function () { - if (!this.backTag) {return; } - var p = document.createElement("p"), - i, - l, - backClick = function (that, idx) { - return function () { - that._goToRoot(idx); - }; - }, - lastA, - lastGt; - for (i = 0, l = this.selectedTreeNodes.length; i < l; i++) { - var a = document.createElement("a"); - a.innerHTML = this.selectedTreeNodes[i].name; - if (i < l - 1) { - a.style.color = "blue"; - a.style.cursor = "pointer"; - a.onclick = backClick(this, i); - } else { - lastA = a; - } - if (i > 0) { - var span = document.createElement('span'); - span.innerHTML = " > "; - p.appendChild(span); - if (i === l - 1) { - lastGt = span; - } - } - p.appendChild(a); - - } - $(lastA).css({"opacity": 0.01, "position": "relative", "left": l === 1 ? 0 : -20}) - .animate({"opacity": 1, "left": 0}, 1000); - this.backTag.innerHTML = ""; - this.backTag.appendChild(p); - }; - - /** - * 计算布局,并重新渲染图表 - */ - Treemap.prototype.reRender = function (options) { - this.clearCanvas(); - this.setOptions(options); - this._create2LevelJson(this.selectedTreeNodes[this.selectedTreeNodes.length - 1]); - this.layout(); - this._setBackTag(); - this.generatePaths(); - }; - - /** - * 计算布局位置,并渲染图表 - */ - Treemap.prototype.render = function (options) { - this.clearCanvas(); - this.setOptions(options); - this._getNodeTheme = undefined; - this.selectedTreeNodes = this.selectedTreeNodes.slice(0, 1); - this._create2LevelJson(this.selectedTreeNodes[0]); - this.layout(); - this._setBackTag(); - this.generatePaths(); - }; - - /** - * 设置自定义事件 - */ - Treemap.prototype.on = function (eventName, callback) { - if ($.inArray(eventName, ["leafNodeClick", "hoverIn", "hoverOut", "mousemove"]) !== -1) { - this.defaults.customEvent[eventName] = callback; - } - }; - - /*! - * 导出Treemap - */ - return Treemap; -}); +/*global d3, $, define */ +/*! + * Treemap兼容定义部分 + */ +;(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];}); + } +})('Treemap', function (require) { + var DataV = require('DataV'); + + /* + * Treemap构造函数,继承自Chart + * Options: + * + * - `width` 数字,图片宽度,默认为750,表示图片高750px + * - `height` 数字,图片高度,默认为500 + * - `showBackTag` 布尔值,回退操作导航条是否显示,默认为 true, 显示;设为false则不显示 + * - `backHeight` 数字,回退操作导航条宽度,默认为20 + * - `level1BorderWidth` 数字,一级方框的边框宽度,默认为1(1px),不建议修改 + * - `level2BorderWidth` 数字,二级方框的边框宽度,默认为1(1px),不建议修改 + * - `fontSizeRatio` 数字,表示图中文字大小。默认为1.0(1倍), 若设为2.0,字体大小会加倍; + * - `customEvent` 函数对象,其中有4个自定义函数。`leafNodeClick` 函数,表示点击叶子节点的事件响应,默认为空函数; `hoverIn` 函数,表示鼠标移进方框的事件响应,默认为空函数; `hoverOut` 函数,表示鼠标移出方框的事件响应,默认为空函数; `mouseover` 函数,表示在方框内移动鼠标的事件响应,默认为设置浮框的内容,可以替换它修改浮框内容; 这些函数可以在创建对象或setOption()时一起设置,也可以通过on函数单独设置。 + * + * Examples: + * create treemap in a dom node with id "chart", width is 500; height is 600px; + * ``` + * var treemap = new Treemap("chart", {"width": 500, "height": 600}); + * ``` + * @param {Object} node The dom node or dom node Id + * @param {Object} options JSON object for determin treemap style + */ + var Treemap = DataV.extend(DataV.Chart, { + initialize: function (node, options) { + this.type = "Treemap"; + this.node = this.checkContainer(node); + + // Properties + this.selectedTreeNodes = [];//array of nodes on the path from root to recent node + this.treeNodeJson = {}; + this.level_ = 2; + + this.floatTag;//浮框对象,这是个可操作的对象。 + + // Canvas + this.defaults.width = 750; + this.defaults.height = 500; + + this.defaults.showBackTag = true; + this.defaults.backHeight = 20; + + this.defaults.level1BorderWidth = 1; + this.defaults.level2BorderWidth = 1; + this.defaults.fontSizeRatio = 1.0; + + //event + this.defaults.customEvent = { + leafNodeClick : function () {}, + hoverIn : function () {}, + hoverOut : function () {}, + mousemove : function () { + var jqNode = this.jqNode, + treemap = jqNode.treemap, + floatTag = treemap.floatTag; + + //set floatTag content + floatTag.html('
      ' + jqNode.treemapNode.name + '
      ' + + '
      ' + jqNode.treemapNode.value + '
      '); + } + }; + + this.setOptions(options); + this.createCanvas(); + } + }); + + /** + * Create dom node relate to treemap + */ + Treemap.prototype.createCanvas = function () { + var conf = this.defaults, + floatStyle, + container = this.node, + backStyle, + canvasStyle; + + this.node.style.position = "relative"; + + if (conf.showBackTag) { + this.backTag = document.createElement("div"); + backStyle = this.backTag.style; + backStyle.width = conf.width + "px"; + backStyle.height = conf.backHeight + "px"; + backStyle.paddingLeft = "5px"; + container.appendChild(this.backTag); + } + + this.canvas = document.createElement("div"); + canvasStyle = this.canvas.style; + canvasStyle.position = "relative"; + canvasStyle.width = conf.width + "px"; + canvasStyle.height = conf.height + "px"; + container.appendChild(this.canvas); + + this.floatTag = DataV.FloatTag()(this.canvas); + + this.floatTag.css({"visibility": "hidden"}); + + //this.canvas.appendChild(this.floatTag); + }; + + /*! + * Get color function according to level 1 node name + * 根据一级节点名获取颜色函数 + */ + Treemap.prototype._changeLevel1NodeColorIndex = function (nodeName) { + if (!this._getNodeTheme) { + this._getNodeTheme = d3.scale.ordinal().range(d3.range(DataV.getColor().length)); + } + return this._getNodeTheme(nodeName); + }; + + /** + * 获取颜色 + * Examples: + * ``` + * // 获取第二种颜色的渐变色。 + * {mode: "gradient", index: 1} + * // 获取最深的离散色。 + * {mode: "random", ratio: 0} + * // 获取最浅的离散色。 + * {mode: "random", ratio: 1} + * // 获取适中的离散色。 + * {mode: "random", ratio: 0.5} + * ``` + * @param {Object} colorJson Way to get color from color theme matrix + * @return {Array} 返回颜色数组 + */ + Treemap.prototype.getColor = function (colorJson) { + var colorMatrix = DataV.getColor(); + var color; + var colorStyle = colorJson || {}; + var colorMode = colorStyle.mode || 'default'; + var i, l; + + switch (colorMode) { + case "multiColorGradient": + //color = d3.interpolateHsl.apply(null, ["red", "blue"]); + //color = d3.interpolateHsl.apply(null, [colorMatrix[0][0], colorMatrix[colorMatrix.length - 1][0]]); + //color = DataV.gradientColor(["#f5f5f6", "#f6f5f5"], 'special'); + //color = DataV.gradientColor([colorMatrix[0][0], colorMatrix[colorMatrix.length - 1][0]], 'special'); + //color = d3.interpolateRgb.apply(null, [colorMatrix[0][0], colorMatrix[colorMatrix.length - 1][0]]); + + color = (function () { + var c = []; + colorMatrix.forEach(function (d, i) { + c.push(d[0]); + }); + return function (ratio) { + var index = (c.length - 1) * ratio; + var floor = Math.floor(index); + var ceil = Math.ceil(index); + if (floor === ceil) { + return c[floor]; + } else { + return d3.interpolateRgb.apply(null, [c[floor], c[ceil]])(index - floor); + } + }; + }()); + //color = d3.interpolateRgb.apply(null, ["green", "purple"]); + break; + 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; + }; + + /* + * 设置数据源 + * Examples: + * treemap数据输入的格式可以是二维数组。例如下面的数组表示2000年4个季度的天数。 + * 第1季度下面还列出了1-3月的天数。数组的第一行为四个固定的字符串"ID","name","size"和"parentID"。 + * 四列数据分别表示层次数据集中各结点的ID,名称,大小和父节点ID。叶子节点必须有大小,根结点不能有父节点ID。各结点的ID、名称必须要有。 + * ``` + * [ + * ["ID", "name", "size", "parentID"], + * [0, "2000", , ], + * [1, "season1", , 0], + * [2, "January", 31, 1], + * [3, "February", 29, 1], + * [4, "Match", 31, 1], + * [5, "season2", 91, 0], + * [6, "season3", 92, 0], + * [7, "season4", 92, 0] + * ] + * ``` + * 数据还可以是json格式。每个结点都有`name`,如果是父节点则还有`children`,如果为叶节点则还有`size`。以上数组数据对应的json数据如下: + * ``` + * { + * "name": "2000", + * "children": [ + * { + * "name": "season1", + * "children": [ + * {"name": "January", "size": 31}, + * {"name": "February", "size": 29}, + * {"name": "Match", "size": 31} + * ] + * }, + * {"name": "season2", "size": 91}, + * {"name": "season3", "size": 92}, + * {"name": "season4", "size": 92}, + * ] + * } + * ``` + * @param {Array|Object} source json or 2-d array + */ + Treemap.prototype.setSource = function (source) { + if (source instanceof Array) { + this.rawData = this._arrayToJson(source); + } else { + this.rawData = source; + } + this.source = this._remapSource(this.rawData); + this.selectedTreeNodes = [this.source[0]]; + }; + + /*! + * Change 2-d array source data to json format. + * @param {Array} array 待转换的二维数组 + * @return {Object} 返回转换后的对象 + */ + Treemap.prototype._arrayToJson = function (array) { + // ID name size parentID + var getColumnIndex = function (columnName) { + var title = array[0]; + var i, l; + for (i = 0, l = title.length; i < l; i++) { + if (title[i] === columnName) { + return i; + } + } + return -1; + }; + + var idx = {}; + var column = ["ID", "name", "size", "parentID"]; + var i, l; + for (i = 0, l = column.length; i < l; i++) { + if ((idx[column[i]] = getColumnIndex(column[i])) === -1) { + throw new Error("no column \'" + column[i] + "\'"); + } + } + + var table = []; + for (i = 1, l = array.length; i < l; i++) { + var line = array[i]; + var tableLine = table[i - 1] = []; + var j, columnL; + for (j = 0, columnL = column.length; j < columnL; j++) { + tableLine.push(line[idx[column[j]]]); + } + } + + var rootID; + var h = {}; + table.forEach(function (d, i) { + if (d[0] === "") { + throw new Error("ID can not be empty(line:" + (i + 1) + ")."); + } + if (!d[3]) { + if (rootID) { + throw new Error("2 or more lines have an empty parentID(line:" + (i + 1) + ")."); + } else { + rootID = d[0]; + } + } + if (h[d[0]]) { + throw new Error("2 or more lines have same ID: " + d[0] + "(line:" + (i + 1) + ")."); + } + h[d[0]] = {name: d[1], size: d[2], child: []}; + }); + if (!rootID) { + throw new Error("No root node defined."); + } + table.forEach(function (d, i) { + if (d[3]) { + var record = h[d[3]]; + if (!record) { + throw new Error("Can not find parent with ID " + d[3] + "(line:" + (i + 1) + ")."); + } + record.child.push(d[0]); + } + }); + var recurse = function (parentID) { + var record = h[parentID]; + if (record.child.length === 0) { + if (isNaN(parseFloat(record.size))) { + throw new Error("Leaf node's size is not a number(name:" + record.name + ")."); + } else { + return {name: record.name, size: record.size}; + } + } else { + var childNode = []; + record.child.forEach(function (d) { + childNode.push(recurse(d)); + }); + return {name: record.name, children: childNode}; + } + }; + return recurse(rootID); + }; + + /*! + * map digit data to layout data format. + * @param source The digit data source from source. + */ + Treemap.prototype._remapSource = function (data) { + var conf = this.defaults; + + var treemap = this._createTreemap(); + var source = treemap.nodes(data); + + var recurse = function (node) { + if (!node.children) { + return node.size; + } + var size = 0; + node.children.forEach(function (d) { + size += parseFloat(recurse(d), 10); + }); + node.size = size; + return node.size; + }; + recurse(source[0]); + + return source; + }; + + /*! + * input a node, return a json tree contains the node's level 1 children and level 2 children; + */ + Treemap.prototype._create2LevelJson = function (node) { + // return json + var recurse = function (node, depth) { + if (depth === 2 && node.refer) { + node = node.refer; + } + if ((!node.children) || depth <= 0) { + return {name: node.name, size: node.size, refer: node}; + } + var childNode = []; + node.children.forEach(function (d) { + childNode.push(recurse(d, depth - 1)); + }); + return {name: node.name, children: childNode, refer: node}; + }; + + this.treeNodeJson = recurse(node, this.level_); + }; + + /*! + * add the clicked leaf to selected tree nodes. + */ + Treemap.prototype._goToLeaf = function (treemapNode) { + this.selectedTreeNodes.push(treemapNode); + this.reRender(); + }; + + /*! + * remove recent leaf node, set leaf node's parent node as new leaf node; + */ + Treemap.prototype._goToRoot = function (idx) { + this.selectedTreeNodes = this.selectedTreeNodes.slice(0, idx + 1); + this.reRender(); + }; + + /*! + * create treemap layout function + */ + Treemap.prototype._createTreemap = function () { + var conf = this.defaults; + return d3.layout.treemap() + .size([conf.width, conf.height]) + .value(function (d) { return d.size; }); + }; + + /*! + * d3 treemap layout + */ + Treemap.prototype.layout = function () { + var treemap = this._createTreemap() + .sort(function (a, b) { return a.value - b.value; }); + this.nodes = treemap.nodes(this.treeNodeJson); + }; + + /*! + * 生成绘制路径 + */ + Treemap.prototype.generatePaths = function () { + //add interactive need + var canvas = this.canvas, + conf = this.defaults, + color, + leafIndex = 0, + leafCount, + colorRatio, + level1Node, + level1NodeArray = [], + funcArray = {}, + i, + l, + level1Count = 0, + level1ColorFun, + goIn = function (event) { + var treemap = event.data.treemap, + treemapNode = event.data.treemapNode, + jqueryNode = event.data.jqueryNode; + jqueryNode.css({'z-index': 30, + "backgroundColor": jqueryNode.color}) + .animate({ + left : 0, + top : 0, + width : treemap.defaults.width, + height : treemap.defaults.height + }, 1000, function () { + treemap._goToLeaf(treemapNode); + }); + }, + + goOut = function (event) { + var treemap = event.data.treemap; + if (treemap.selectedTreeNodes.length > 1) { + treemap._goToRoot(treemap.selectedTreeNodes.length - 2); + } + return false; + }, + + level1HoverIn = function (index, level1NodeArray) { + return function () { + level1NodeArray.forEach(function (d, i, Array) { + if (i === index) { + d.css("font-size", Array[i].fontSizeValue * 6 / 5 + "px"); + if (d.treemapNode.children) { + Array[i].css("backgroundColor", ''); + } + } else { + Array[i].css("backgroundColor", + d3.interpolateRgb.apply(null, [Array[i].color, "#fff"])(0.4)); + } + }); + level1NodeArray[0].treemap.floatTag.css({"visibility" : "visible"}); + }; + }, + + level1HoverOut = function (level1NodeArray) { + return function () { + level1NodeArray.forEach(function (d, i, Array) { + Array[i].css("backgroundColor", Array[i].color) + .css("font-size", Array[i].fontSizeValue + "px"); + }); + level1NodeArray[0].treemap.floatTag.css({"visibility" : "hidden"}); + }; + }, + + level0HoverIn = function () { + this.jqNode.treemap.floatTag.css({"visibility" : "visible"}); + }, + + level0HoverOut = function () { + this.jqNode.treemap.floatTag.css({"visibility" : "hidden"}); + }, + + customEvent = function (f, o) { + return function () { + f.call(o); + }; + }; + + $(canvas).append(this.floatTag);//canvas clear before draw, add floatTag. + + for (i = 0, l = this.nodes.length; i < l; i++) { + var d = this.nodes[i],//treemap node + borderWidth, + w, + h, + left, + top, + depthColor, + depthColorHsl, + jqNode = $(document.createElement("div")); + + if (!(d.parent && d.parent.parent)) { + //level 0 and 1 + color = this.getColor({mode: "gradient", index: this._changeLevel1NodeColorIndex(d.name)}); + leafIndex = -1; + leafCount = d.children ? d.children.length : 0; + colorRatio = 0.5; + + if (d.parent) { + //only level 1 + level1NodeArray.push(jqNode); + level1Node = jqNode; + } + jqNode.color = color(colorRatio); + if (d.parent) { + //level 1 + if (this.selectedTreeNodes.length === 1) { + //root + if (!level1ColorFun) { + level1ColorFun = this.getColor({mode: "multiColorGradient"}); + } + } else { + if (!level1ColorFun) { + depthColor = this.selectedTreeNodes[this.selectedTreeNodes.length - 1].color; + depthColorHsl = d3.hsl(depthColor); + level1ColorFun = d3.interpolateHsl.apply(null, [ + d3.hsl((depthColorHsl.h + 180) % 360, depthColorHsl.s, depthColorHsl.l).toString(), + depthColor + ]); + } + } + jqNode.color = d.parent.children.length === 1 + ? level1ColorFun(0) + : level1ColorFun((level1Count++) / (d.parent.children.length - 1)); + color = d3.interpolateRgb.apply(null, [jqNode.color, + d3.interpolateRgb.apply(null, [jqNode.color, "#fff"])(0.5)]); + } else { + //level 0 + if (this.selectedTreeNodes.length > 1) { + jqNode.color = this.selectedTreeNodes[this.selectedTreeNodes.length - 1].color; + } + } + } else { + //level 2 + leafIndex += 1; + colorRatio = leafCount === 1 ? 0 : leafIndex / (leafCount - 1); + jqNode.color = color(colorRatio); + } + jqNode.treemapNode = d; + d.color = jqNode.color; + jqNode.treemap = this; + jqNode[0].jqNode = jqNode; + jqNode.appendTo($(canvas)); + + jqNode.css("borderStyle", "solid") + .css("borderColor", "#d6d6d6") + .css("overflow", "hidden") + .css("fontFamily", '黑体') + //.css("fontFamily", 'sans-serif') + .css("position", "absolute") + .css("textIndent", "2px") + .css("backgroundColor", jqNode.color) + //.css("color", "#fff"); + .css("color", "white"); + //.css("textAlign", "center"); + + if (!d.parent) { + // level 0 node + if (!d.children) { + jqNode.html(d.name); + } + } else if (d.parent && !d.parent.parent) { + // level 1 node + jqNode.html(d.name) + .css("zIndex", 20); + if (d.children) { + //jqNode.css("backgroundColor", ''); + jqNode.css("backgroundColor", jqNode.color); + } + } else { + // level 2 node + jqNode.css("borderColor", "rgba(255, 255, 255, 0.5)"); + } + + // position + // border do not need adjust, still 1 px; + borderWidth = 1; + w = Math.max(0, d.dx - borderWidth); + h = Math.max(0, d.dy - borderWidth); + jqNode.fontSizeValue = Math.max(conf.fontSizeRatio + * parseInt(60 * Math.sqrt(w * h / conf.width / conf.height), 10), + 12); + jqNode.css("borderWidth", borderWidth + "px") + .css("left", d.x + "px") + .css("top", d.y + "px") + .css("width", w + "px") + .css("height", h + "px") + .css("fontSize", jqNode.fontSizeValue + "px"); + //.css("lineHeight", Math.max(0, d.dy - borderWidth ) + "px"); + + if (d.parent && !d.parent.parent) { + //level 1 + jqNode.css("cursor", "pointer"); + jqNode.clone().insertBefore(jqNode).css("opacity", 0.01); + jqNode.click({"treemap": this, "treemapNode": d, "jqueryNode": jqNode}, goIn); + jqNode.mouseover(level1HoverIn(level1NodeArray.length - 1, level1NodeArray)); + jqNode.mouseout(level1HoverOut(level1NodeArray)); + jqNode.mousemove(this.defaults.customEvent.mousemove); + } + + if (!d.parent) { + //level 0; leaf node + jqNode.mouseover(level0HoverIn); + jqNode.mouseout(level0HoverOut); + jqNode.mousemove(this.defaults.customEvent.mousemove); + } + + jqNode.bind("contextmenu", {"treemap": this}, goOut); + + // custom interacitves + var o = {treemapNode : d, + node : jqNode, + name : d.name, + parent : d.parent, + value : d.value + }; + + if (d.parent && (!d.parent.parent) && (!d.children)) { + jqNode.click(customEvent(this.defaults.customEvent.leafNodeClick, o)); + } + + jqNode.mouseover(customEvent(this.defaults.customEvent.hoverIn, o)); + + jqNode.mouseout(customEvent(this.defaults.customEvent.hoverOut, o)); + + //canvas.appendChild(jqNode[0]); + } + }; + + /** + * 清除画布 + */ + Treemap.prototype.clearCanvas = function () { + var canvas = this.canvas; + canvas.innerHTML = ""; + }; + + /*! + * set back navi link + */ + Treemap.prototype._setBackTag = function () { + if (!this.backTag) {return; } + var p = document.createElement("p"), + i, + l, + backClick = function (that, idx) { + return function () { + that._goToRoot(idx); + }; + }, + lastA, + lastGt; + for (i = 0, l = this.selectedTreeNodes.length; i < l; i++) { + var a = document.createElement("a"); + a.innerHTML = this.selectedTreeNodes[i].name; + if (i < l - 1) { + a.style.color = "blue"; + a.style.cursor = "pointer"; + a.onclick = backClick(this, i); + } else { + lastA = a; + } + if (i > 0) { + var span = document.createElement('span'); + span.innerHTML = " > "; + p.appendChild(span); + if (i === l - 1) { + lastGt = span; + } + } + p.appendChild(a); + + } + $(lastA).css({"opacity": 0.01, "position": "relative", "left": l === 1 ? 0 : -20}) + .animate({"opacity": 1, "left": 0}, 1000); + this.backTag.innerHTML = ""; + this.backTag.appendChild(p); + }; + + /** + * 计算布局,并重新渲染图表 + */ + Treemap.prototype.reRender = function (options) { + this.clearCanvas(); + this.setOptions(options); + this._create2LevelJson(this.selectedTreeNodes[this.selectedTreeNodes.length - 1]); + this.layout(); + this._setBackTag(); + this.generatePaths(); + }; + + /** + * 计算布局位置,并渲染图表 + */ + Treemap.prototype.render = function (options) { + this.clearCanvas(); + this.setOptions(options); + this._getNodeTheme = undefined; + this.selectedTreeNodes = this.selectedTreeNodes.slice(0, 1); + this._create2LevelJson(this.selectedTreeNodes[0]); + this.layout(); + this._setBackTag(); + this.generatePaths(); + }; + + /** + * 设置自定义事件 + */ + Treemap.prototype.on = function (eventName, callback) { + if ($.inArray(eventName, ["leafNodeClick", "hoverIn", "hoverOut", "mousemove"]) !== -1) { + this.defaults.customEvent[eventName] = callback; + } + }; + + /*! + * 导出Treemap + */ + return Treemap; +});