/*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 * 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); this.defaults = {}; // Properties this.selectedTreeNodes = [];//array of nodes on the path from root to recent node this.treeNodeJson = {}; this.level_ = 2; this.defaults.level1BorderWidth = 1; this.defaults.level2BorderWidth = 1; this.defaults.fontSizeRatio = 1.0; // Canvas this.defaults.showBackTag = true; this.defaults.backHeight = 20; this.defaults.width = 750; this.defaults.height = 500; 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"}); }, mousemove = function () { var jqNode = this.jqNode, treemap = jqNode.treemap, floatTag = treemap.floatTag; //set floatTag content floatTag.html('