UNPKG

vislite

Version:

灵活、快速、简单的数据可视化交互式跨端前端库

457 lines (432 loc) 17.7 kB
/*! * TreeLayout of VISLite JavaScript Library v1.3.0 * git+https://github.com/oi-contrib/VISLite.git */ /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol */ var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var toInnerTree = (function (initTree, config) { var tempTree = {}; var temp = config.root(initTree); var id, rid; id = rid = config.id(temp); tempTree[id] = { "data": temp, "pid": null, "id": id, "isOpen": true, "show": true, "deg": 0, "children": [] }; var num = 1; (function createTree(pdata, pid) { var children = config.children(pdata, initTree); num += children ? children.length : 0; for (var flag = 0; children && flag < children.length; flag++) { id = config.id(children[flag]); tempTree[pid].children.push(id); tempTree[id] = { "data": children[flag], "pid": pid, "id": id, "isOpen": true, "show": true, "deg": 0, "children": [] }; createTree(children[flag], id); } })(temp, id); return { rid: rid, value: tempTree, num: num }; }); function toPlainTree (initTree, config, noOpens) { var treeData = toInnerTree(initTree, config); var alltreedata = treeData.value; var rootid = treeData.rid; if (treeData.num == 1) { alltreedata[rootid].left = 0.5; alltreedata[rootid].top = 0.5; alltreedata[rootid].show = true; return { deep: 1, node: alltreedata, root: rootid, size: 1 }; } else { var beforeDis_1 = []; var size_1 = 0, maxDeep_1 = 0; if (noOpens[rootid]) { alltreedata[rootid].left = 0.5; alltreedata[rootid].top = 0.5; alltreedata[rootid].show = true; size_1 = 1; } else { (function positionCalc(pNode, deep) { if (deep > maxDeep_1) maxDeep_1 = deep; var flag = 0; if (!noOpens[pNode.id]) { for (flag = 0; flag < pNode.children.length; flag++) positionCalc(alltreedata[pNode.children[flag]], deep + 1); } alltreedata[pNode.id].left = deep + 0.5; if (flag == 0) { if (beforeDis_1[deep] == void 0) beforeDis_1[deep] = -0.5; if (beforeDis_1[deep - 1] == void 0) beforeDis_1[deep - 1] = -0.5; alltreedata[pNode.id].top = beforeDis_1[deep] + 1; var pTop = beforeDis_1[deep] + 1 + (alltreedata[pNode.pid].children.length - 1) * 0.5; if (pTop - 1 < beforeDis_1[deep - 1]) alltreedata[pNode.id].top = beforeDis_1[deep - 1] + 1 - (alltreedata[pNode.pid].children.length - 1) * 0.5; } else { alltreedata[pNode.id].top = (alltreedata[pNode.children[0]].top + alltreedata[pNode.children[flag - 1]].top) * 0.5; } if (alltreedata[pNode.id].top <= beforeDis_1[deep]) { var needUp_1 = beforeDis_1[deep] + 1 - alltreedata[pNode.id].top(function doUp(_pid, _deep) { alltreedata[_pid].top += needUp_1; if (beforeDis_1[_deep] < alltreedata[_pid].top) beforeDis_1[_deep] = alltreedata[_pid].top; for (var _flag = 0; _flag < alltreedata[_pid].children.length; _flag++) { doUp(alltreedata[_pid].children[_flag], _deep + 1); } })(pNode.id, deep); } beforeDis_1[deep] = alltreedata[pNode.id].top; if (alltreedata[pNode.id].top + 0.5 > size_1) size_1 = alltreedata[pNode.id].top + 0.5; })(alltreedata[rootid], 0); } for (var key in noOpens) { if (noOpens[key]) { alltreedata[key].isOpen = false; (function updateHidden(pid, left, top) { for (var index = 0; index < alltreedata[pid].children.length; index++) { alltreedata[alltreedata[pid].children[index]].left = left; alltreedata[alltreedata[pid].children[index]].top = top; alltreedata[alltreedata[pid].children[index]].show = false; updateHidden(alltreedata[pid].children[index], left, top); } })(key, alltreedata[key].left, alltreedata[key].top); } } return { "node": alltreedata, "root": rootid, "size": size_1, "deep": maxDeep_1 + 1 }; } } function initOption(setOption, defaultOption) { for (var key in setOption) { defaultOption[key] = setOption[key]; } return defaultOption; } var Tree = (function () { function Tree(config) { if (config === void 0) { config = {}; } this.name = 'Tree'; this.__config = initOption(config, { root: function (initTree) { return initTree; }, children: function (parentTree) { return parentTree.children; }, id: function (treedata) { return treedata.name; } }); } Tree.prototype.use = function (initTree, noOpens) { if (noOpens === void 0) { noOpens = {}; } return toPlainTree(initTree, this.__config, noOpens); }; return Tree; }()); //当前正在运动的动画的tick函数堆栈 var $timers = []; //唯一定时器的定时间隔 var $interval = 13; //定时器ID var $timerId; /** * 动画轮播 * @param {function} doback 轮询函数,有一个形参deep,0-1,表示执行进度 * @param {number} duration 动画时长,可选 * @param {function} callback 动画结束回调,可选,有一个形参deep,0-1,表示执行进度 * * @returns {function} 返回一个函数,调用该函数,可以提前结束动画 */ function animation(doback, duration, callback) { if (arguments.length < 2) duration = 400; if (arguments.length < 3) callback = function () { }; var clock = { //把tick函数推入堆栈 "timer": function (tick, duration, callback) { if (!tick) { throw new Error('Tick is required!'); } var id = new Date().valueOf() + "_" + (Math.random() * 1000).toFixed(0); $timers.push({ "id": id, "createTime": new Date(), "tick": tick, "duration": duration, "callback": callback }); clock.start(); return id; }, //开启唯一的定时器timerId "start": function () { if (!$timerId) { $timerId = setInterval(clock.tick, $interval); } }, //被定时器调用,遍历timers堆栈 "tick": function () { var createTime, flag, tick, callback, timer, duration, passTime, timers = $timers; $timers = []; $timers.length = 0; for (flag = 0; flag < timers.length; flag++) { //初始化数据 timer = timers[flag]; createTime = timer.createTime; tick = timer.tick; duration = timer.duration; callback = timer.callback; //执行 passTime = (+new Date().valueOf() - createTime.valueOf()) / duration; passTime = passTime > 1 ? 1 : passTime; tick(passTime); if (passTime < 1 && timer.id) { //动画没有结束再添加 $timers.push(timer); } else { callback(passTime); } } if ($timers.length <= 0) { clock.stop(); } }, //停止定时器,重置timerId=null "stop": function () { if ($timerId) { clearInterval($timerId); $timerId = null; } } }; var id = clock.timer(function (deep) { //其中deep为0-1,表示改变的程度 doback(deep); }, duration, callback); // 返回一个函数 // 用于在动画结束前结束动画 return function () { var i; for (i in $timers) { if ($timers[i].id == id) { $timers[i].id = void 0; return; } } }; } function rotate (cx, cy, deg, x, y) { var cos = Math.cos(deg), sin = Math.sin(deg); return [ (x - cx) * cos - (y - cy) * sin + cx, (x - cx) * sin + (y - cy) * cos + cy ]; } var TreeLayout = (function (_super) { __extends(TreeLayout, _super); function TreeLayout() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.name = 'TreeLayout'; _this.__option = { offsetX: 0, offsetY: 0, duration: 500, type: "plain", direction: "LR", x: 100, y: 100, width: 100, height: 100, radius: 100 }; _this.__noOpens = {}; return _this; } TreeLayout.prototype.setOption = function (option) { initOption(option, this.__option); return this; }; TreeLayout.prototype.use = function (initTree, noOpens) { if (noOpens === void 0) { noOpens = {}; } var tree = _super.prototype.use.call(this, initTree, noOpens); if (this.__option.offsetX != 0 || this.__option.offsetY != 0) { for (var key in tree.node) { if (!tree.node[key].show) { var deep = 0, pid = key; do { pid = tree.node[pid].pid; deep++; } while (!tree.node[pid].show); tree.node[key].left += this.__option.offsetX * deep; tree.node[key].top += this.__option.offsetY * deep; } } } if (this.__option.type == 'rect') { if (this.__option.direction == 'LR' || this.__option.direction == "RL") { var perW = this.__option.height / tree.size; var perD = this.__option.width / (tree.deep - 1); var balanceW = this.__option.y - this.__option.height * 0.5; var flag = this.__option.direction == 'LR' ? 1 : -1; for (var key in tree.node) { if (tree.deep == 1) { tree.node[key].left = this.__option.x + this.__option.width * 0.5 * flag; tree.node[key].top = this.__option.y; } else { tree.node[key].left = this.__option.x + (tree.node[key].left - 0.5) * perD * flag; tree.node[key].top = tree.node[key].top * perW + balanceW; } } } else if (this.__option.direction == 'TB' || this.__option.direction == "BT") { var perW = this.__option.width / tree.size; var perD = this.__option.height / (tree.deep - 1); var balanceW = this.__option.x - this.__option.width * 0.5; var flag = this.__option.direction == 'TB' ? 1 : -1; for (var key in tree.node) { tree.node[key].deg = this.__option.direction == 'TB' ? Math.PI * 0.5 : Math.PI * -0.5; if (tree.deep == 1) { tree.node[key].left = this.__option.x; tree.node[key].top = this.__option.y + this.__option.height * 0.5 * flag; } else { var left = tree.node[key].left; tree.node[key].left = tree.node[key].top * perW + balanceW; tree.node[key].top = this.__option.y + (left - 0.5) * perD * flag; } } } } else if (this.__option.type == 'circle') { var cx = this.__option.x, cy = this.__option.y; var deg = Math.PI * 2 / tree.size; var per = this.__option.radius / (tree.deep - 1); for (var key in tree.node) { if (tree.node[key].left == 0.5) { tree.node[key].left = cx; tree.node[key].top = cy; } else { var position = rotate(cx, cy, deg * tree.node[key].top, cx + (tree.node[key].left - 0.5) * per, cy); tree.node[key].deg = deg * tree.node[key].top; tree.node[key].left = position[0]; tree.node[key].top = position[1]; } } } return tree; }; TreeLayout.prototype.bind = function (initTree, renderBack, noOpens) { if (noOpens === void 0) { noOpens = {}; } this.__rback = renderBack; this.__oralTree = initTree; this.__noOpens = noOpens; this.__preTree = this.use(this.__oralTree, this.__noOpens); this.__rback(this.__preTree); return this; }; TreeLayout.prototype.unbind = function () { this.__rback = function () { return null; }; this.__oralTree = null; this.__preTree = null; this.__noOpens = {}; return this; }; TreeLayout.prototype.doUpdate = function () { var _this = this; var newTree = this.use(this.__oralTree, this.__noOpens); var cacheTree = JSON.parse(JSON.stringify(newTree)); animation(function (deep) { if (_this.__preTree) { for (var key in cacheTree.node) { if (newTree.node[key].show || _this.__preTree.node[key].show) { cacheTree.node[key].show = true; cacheTree.node[key].left = _this.__preTree.node[key].left + (newTree.node[key].left - _this.__preTree.node[key].left) * deep; cacheTree.node[key].top = _this.__preTree.node[key].top + (newTree.node[key].top - _this.__preTree.node[key].top) * deep; } } } _this.__rback(cacheTree); }, this.__option.duration, function () { _this.__preTree = newTree; _this.__rback(_this.__preTree); }); return this; }; TreeLayout.prototype.closeNode = function (id) { if (!this.__preTree) return this; this.__noOpens[id] = true; this.doUpdate(); return this; }; TreeLayout.prototype.openNode = function (id) { if (!this.__preTree) return this; this.__noOpens[id] = false; this.doUpdate(); return this; }; TreeLayout.prototype.toggleNode = function (id) { if (!this.__preTree) return this; this.__noOpens[id] = !this.__noOpens[id]; this.doUpdate(); return this; }; return TreeLayout; }(Tree)); export { TreeLayout as default };