UNPKG

image2d

Version:

🍇 使用ECMAScript绘制二维图片。Drawing Two-Dimensional Pictures Using ECMAScript.

623 lines (622 loc) 83.5 kB
/*! * image2D - 🍇 使用ECMAScript绘制二维图片。Drawing Two-Dimensional Pictures Using ECMAScript. * git+https://github.com/hai2007/image2D.git * * For online documents, please visit * https://hai2007.gitee.io/image2d/index.html * * author 你好2007 * * version 1.16.5 * * build Thu Apr 11 2019 * * Copyright hai2007 < https://hai2007.gitee.io/sweethome/ > * Released under the MIT license * * Date:Tue Jul 22 2025 19:29:43 GMT+0800 (China Standard Time) */ "use strict";(function(){'use strict'; /** * 初始化配置文件 * @param {Json} init 默认值 * @param {Json} data * @return {Json} */var initConfig$1=function initConfig$1(init,data){for(var key in data) {try{init[key] = data[key];}catch(e) {throw new Error("Illegal property value!");}}return init;}; // 命名空间路径 var NAMESPACE={"svg":"http://www.w3.org/2000/svg","xlink":"http://www.w3.org/1999/xlink"}; // 正则表达式 var REGEXP={ // 空白字符:http://www.w3.org/TR/css3-selectors/#whitespace "whitespace":"[\\x20\\t\\r\\n\\f]", // 空格外的空白字符 "blank":"[\\n\\f\\r]", // 标志符:http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier "identifier":"(?:\\\\.|[\\w-]|[^\0-\\xa0])+"}; // 记录需要使用xlink命名空间常见的xml属性 var XLINK_ATTRIBUTE=["href","title","show","type","role","actuate"]; /** * 判断一个值是不是Object。 * * @param {*} value 需要判断类型的值 * @returns {boolean} 如果是Object返回true,否则返回false */function _isObject(value){var type=typeof value;return value != null && (type === 'object' || type === 'function');}var toString=Object.prototype.toString; /** * 获取一个值的类型字符串[object type] * * @param {*} value 需要返回类型的值 * @returns {string} 返回类型字符串 */function getType(value){if(value == null){return value === undefined?'[object Undefined]':'[object Null]';}return toString.call(value);} /** * 判断一个值是不是number。 * * @param {*} value 需要判断类型的值 * @returns {boolean} 如果是number返回true,否则返回false */function _isNumber(value){return typeof value === 'number' || value !== null && typeof value === 'object' && getType(value) === '[object Number]';} /** * 判断一个值是不是String。 * * @param {*} value 需要判断类型的值 * @returns {boolean} 如果是String返回true,否则返回false */function _isString(value){var type=typeof value;return type === 'string' || type === 'object' && value != null && !Array.isArray(value) && getType(value) === '[object String]';} /** * 判断一个值是不是Function。 * * @param {*} value 需要判断类型的值 * @returns {boolean} 如果是Function返回true,否则返回false */function _isFunction(value){if(!_isObject(value)){return false;}var type=getType(value);return type === '[object Function]' || type === '[object AsyncFunction]' || type === '[object GeneratorFunction]' || type === '[object Proxy]';} /** * 判断一个值是不是一个朴素的'对象' * 所谓"纯粹的对象",就是该对象是通过"{}"或"new Object"创建的 * * @param {*} value 需要判断类型的值 * @returns {boolean} 如果是朴素的'对象'返回true,否则返回false */function _isPlainObject(value){if(value === null || typeof value !== 'object' || getType(value) != '[object Object]'){return false;} // 如果原型为null if(Object.getPrototypeOf(value) === null){return true;}var proto=value;while(Object.getPrototypeOf(proto) !== null) {proto = Object.getPrototypeOf(proto);}return Object.getPrototypeOf(value) === proto;}var domTypeHelp=function domTypeHelp(types,value){return value !== null && typeof value === 'object' && types.indexOf(value.nodeType) > -1 && !_isPlainObject(value);}; /*! * 💡 - 值类型判断方法 * https://github.com/hai2007/tool.js/blob/master/type.js * * author hai2007 < https://hai2007.gitee.io/sweethome > * * Copyright (c) 2020-present hai2007 走一步,再走一步。 * Released under the MIT license */var isObject=_isObject;var isNumber=_isNumber;var isString=_isString; // 引用类型 var isFunction=_isFunction;var isArray=function isArray(input){return Array.isArray(input);}; // 结点类型 var isElement=function isElement(input){return domTypeHelp([1,9,11],input);};var isText=function isText(input){return domTypeHelp([3],input);}; /** * 设置svg字符串 * @param {dom} target * @param {string} svgstring */var setSVG=function setSVG(target,svgstring){if('innerHTML' in SVGElement.prototype === false || 'innerHTML' in SVGSVGElement.prototype === false){(function(){ // 创建一个非svg结点,用例帮助解析 // 这样比直接解析字符串简单 var frame=document.createElement("div");frame.innerHTML = svgstring;var toSvgNode=function toSvgNode(htmlNode){ // 创建svg结点,并挂载属性 var svgNode=document.createElementNS(NAMESPACE.svg,htmlNode.tagName.toLowerCase());var attrs=htmlNode.attributes;for(var i=0;attrs && i < attrs.length;i++) { // 是否是特殊属性目前靠手工登记 if(XLINK_ATTRIBUTE.indexOf(attrs[i].nodeName) >= 0){ // 针对特殊的svg属性,追加命名空间 svgNode.setAttributeNS(NAMESPACE.xlink,'xlink:' + attrs[i].nodeName,htmlNode.getAttribute(attrs[i].nodeName));}else {svgNode.setAttribute(attrs[i].nodeName,htmlNode.getAttribute(attrs[i].nodeName));}}return svgNode;};var rslNode=toSvgNode(frame.firstChild);(function toSVG(pnode,svgPnode){var node=pnode.firstChild; // 如果是文本结点 if(isText(node)){svgPnode.textContent = pnode.innerText;return;} // 不是文本结点,就拼接 while(node) {var svgNode=toSvgNode(node);svgPnode.appendChild(svgNode);if(node.firstChild)toSVG(node,svgNode);node = node.nextSibling;}})(frame.firstChild,rslNode); // 拼接 target.appendChild(rslNode);})();}else { // 如果当前浏览器提供了svg类型结点的innerHTML,我们还是使用浏览器提供的 target.innerHTML = svgstring;}}; // 变成指定类型的结点 // type可以取: // 1.'HTML',html结点 // 2.'SVG',svg结点(默认值) var toNode=function toNode(template,type){var frame=undefined,childNodes=undefined,frameTagName='div';if(type === 'html' || type === 'HTML'){ // 大部分的标签可以直接使用div作为容器 // 部分特殊的需要特殊的容器标签 if(/^<tr[> ]/.test(template)){frameTagName = "tbody";}else if(/^<th[> ]/.test(template) || /^<td[> ]/.test(template)){frameTagName = "tr";}else if(/^<thead[> ]/.test(template) || /^<tbody[> ]/.test(template)){frameTagName = "table";}frame = document.createElement(frameTagName);frame.innerHTML = template; // 比如tr标签,它应该被tbody或thead包含 // 如果采用别的标签,比如div,这类标签无法生成 // 为了方便校对,这里给出提示 if(!/</.test(frame.innerHTML)){throw new Error('This template cannot be generated using div as a container:' + template + "\nPlease contact us: https://github.com/hai2007/image2D/issues");}}else {frame = document.createElementNS(NAMESPACE.svg,'svg'); // 部分浏览器svg元素没有innerHTML setSVG(frame,template);}childNodes = frame.childNodes;for(var i=0;i < childNodes.length;i++) {if(isElement(childNodes[i]))return childNodes[i];}}; /** * 变成结点 * @param {string} template * @param {string} type * @return {dom} 返回结点 */function toNode$1(template,type){ // 把传递元素类型和标记进行统一处理 if(new RegExp("^" + REGEXP.identifier + "$").test(template))template = "<" + template + "></" + template + ">";var mark=/^<([^(>| )]+)/.exec(template)[1]; // 画布canvas特殊知道,一定是html if("canvas" === mark.toLowerCase())type = 'HTML'; // 此外,如果没有特殊设定,规定一些标签是html标签 if(!isString(type) && [ // 三大display元素 "div","span","p", // 小元素 "em","i", // 关系元素 "table","ul","ol","dl","dt","li","dd", // 表单相关 "form","input","button","textarea", // H5结构元素 "header","footer","article","section", // 标题元素 "h1","h2","h3","h4","h5","h6", // 替换元素 "image","video","iframe","object", // 资源元素 "style","script","link", // table系列 "tr","td","th","tbody","thead"].indexOf(mark.toLowerCase()) >= 0)type = 'HTML';return toNode(template,type);} /** * 在指定上下文查找结点 * @param {string|dom|array|function|image2D} selector 选择器,必输 * @param {dom|'html'|'svg'} context 查找上下文,或标签类型,必输 * @return {array|image2D} 结点数组 * * 特别注意: * 1.id选择器或者传入的是维护的结点,查找上下文会被忽略 * 2.如果selector传入的是一个字符串模板,context可选,其表示模板类型 */function sizzle(selector,context){ // 如果是字符串 // context如果是字符串(应该是'html'或'svg')表示这是生成结点,也走这条路线 if(isString(context) || isString(selector)){selector = selector.trim().replace(new RegExp(REGEXP.blank,'g'),''); // 如果以'<'开头表示是字符串模板 if(typeof context == 'string' || /^</.test(selector)){var node=toNode$1(selector,context);if(isElement(node))return [node];else return [];} // *表示查找全部 else if(selector === '*'){return context.getElementsByTagName('*');}var id=selector.match(new RegExp('#' + REGEXP.identifier,'g')); // ID选择器 // 此选择器会忽略上下文 if(id){var node=document.getElementById(id[0].replace('#',''));if(isElement(node))return [node];else return [];}var cls=selector.match(new RegExp('\\.' + REGEXP.identifier,'g')),tag=selector.match(new RegExp('^' + REGEXP.identifier)); // 结点和class混合选择器 if(tag || cls){var allNodes=context.getElementsByTagName(tag?tag[0]:"*"),temp=[];for(var i=0;i < allNodes.length;i++) {var clazz=" " + allNodes[i].getAttribute('class') + " ",flag=true;for(var j=0;cls && j < cls.length;j++) {if(!clazz.match(" " + cls[j].replace('.','') + " ")){flag = false;break;}}if(flag)temp.push(allNodes[i]);}return temp;} // 未知情况,报错 else {throw new Error('Unsupported selector:' + selector);}} // 如果是结点 else if(isElement(selector)){return [selector];} // 如果是数组 // 数组中的内容如果不是结点会直接被忽略 else if(selector && (selector.constructor === Array || selector.constructor === HTMLCollection || selector.constructor === NodeList)){var temp=[];for(var i=0;i < selector.length;i++) { // 如果是结点 if(isElement(selector[i]))temp.push(selector[i]); // 如果是image2D对象 else if(selector[i] && selector[i].constructor === image2D){for(var j=0;j < selector[i].length;j++) {temp.push(selector[i][j]);}}}return temp;} // 如果是image2D对象 else if(selector && selector.constructor === image2D){return selector;} // 如果是函数 else if(isFunction(selector)){var allNodes=context.getElementsByTagName('*'),temp=[];for(var i=0;i < allNodes.length;i++) { // 如果选择器函数返回true,表示当前面对的结点被接受 if(selector(allNodes[i]))temp.push(allNodes[i]);}return temp;} // 未知情况,报错 else {throw new Error('Unknown selector:' + selector);}} /** * 设计需求是: * image2D和image2D(selector[, context]) * 分别表示绘图类和绘图对象 * * 题外:为什么不选择image2D和new image2D(selector[, context])? * 只是感觉没有前面的写法用起来简洁 * * 为了实现需求,第一反应是: * let image2D=function(selector,context){ * return new image2D(); * }; * * 在image2D上挂载静态方法,在image2D.prototype上挂载对象方法, * 看起来稳的很,其实这明显是一个死循环。 * * 为了解决这个问题,我们在image2D的原型上定义了一个方法: * image2D.prototype.init=function(selector,context){ * return this; * }; * * 执行下面的方法: * let temp=image2D.prototype.init(selector, context); * 上面返回的temp很明显就是image2D.prototype,其实就是image2D对象 * (例如:new A(),其实就是取A.prototype,这样对比就很好理解了) * * 因此可以改造代码如下: * * 这样image2D和new image2D(selector[, context])就分别表示类和对象。 * * 问:看起来是不是实现了? * 答:是的,实现了。 * 问:可是总感觉有点不好,说不出为什么。 * 答:是不是感觉image2D()打印出来的东西有点多? * 问:是的。 * * 事实上,因为直接取image2D.prototype作为new image2D(), * 理论上说,使用上区别不大,唯一不足的是, * 挂载在image2D.prototype上的方法会在打印image2D对象的时候看见,不舒服。 * * 为了看起来好看些,代码再次改造: * let image2D = function (selector, context) { * return new image2D.prototype.init(selector, context); * }; * * 为了让image2D(selector, context)返回的是image2D对象,需要修改image2D.prototype.init的原型: * image2D.prototype.init.prototype = image2D.prototype; * * 这样: * image2D(selector, context) == * return new image2D.prototype.init(selector, context) == * image2D.prototype.init.prototype == * image2D.prototype == * new image2D(selector, context) * * 此时需求就实现了, * 而且打印image2D(selector, context)的时候, * 对象上的方法都在原型上,看起来就比较舒服了。 */var image2D=function image2D(selector,context){return new image2D.prototype.init(selector,context);};image2D.prototype.init = function(selector,context){ // 如果没有传递,默认使用document作为上下文 this.context = context = context || document; // 使用sizzle获取需要维护的结点,并把结点维护到image2D对象中 var nodes=sizzle(selector,context),flag=undefined;for(flag = 0;flag < nodes.length;flag++) {this[flag] = nodes[flag];} // 设置结点个数 this.length = nodes.length;return this;}; // 扩展方法 // 在image2D和image2D.prototype上分别调用extend方法就可以在类和对象上扩展方法了 image2D.prototype.extend = image2D.extend = function(){var target=arguments[0] || {};var source=arguments[1] || {};var length=arguments.length; /* * 确定复制目标和源 */if(length === 1){ //如果只有一个参数,目标对象是自己 source = target;target = this;}if(!isObject(target)){ //如果目标不是对象或函数,则初始化为空对象 target = {};} /* * 复制属性到对象上面 */for(var key in source) {try{target[key] = source[key];}catch(e) { // 为什么需要try{}catch(e){}? // 一些对象的特殊属性不允许覆盖,比如name // 执行:image2D.extend({'name':'新名称'}) // 会抛出TypeError throw new Error("Illegal property key:" + key + "!");}}return target;};image2D.prototype.init.prototype = image2D.prototype; /*! * 🔪 - 基本的树结构位置生成算法 * https://github.com/hai2007/algorithm.js/blob/master/tree.js * * author hai2007 < https://hai2007.gitee.io/sweethome > * * Copyright (c) 2020-present hai2007 走一步,再走一步。 * Released under the MIT license */function treeLayout$1(_config){ /** * 无论绘制的树结构是什么样子的 * 计算时都假想目标树的样子如下: * 1.根结点在最左边,且上下居中 * 2.树是从左往右生长的结构 * 3.每个结点都是一块1*1的正方形,top和left分别表示正方形中心的位置 */var config={}, // 维护的树 alltreedata, // 根结点ID rootid; /** * 把内部保存的树结点数据 * 计算结束后会调用配置的绘图方法 */var update=function update(){var beforeDis=[],size=0,maxDeep=0;(function positionCalc(pNode,deep){if(deep > maxDeep)maxDeep = deep;var flag;for(flag = 0;flag < pNode.children.length;flag++) // 因为全部的子结点的位置确定了,父结点的y位置就是子结点的中间位置 // 因此有子结点的,先计算子结点 positionCalc(alltreedata[pNode.children[flag]],deep + 1); // left的位置比较简单,deep从0开始编号 // 比如deep=0,第一层,left=0+0.5=0.5,也就是根结点 alltreedata[pNode.id].left = deep + 0.5;if(flag == 0){ // beforeDis是一个数组,用以记录每一层此刻top下边缘(每一层是从上到下) // 比如一层的第一个,top值最小可以取top=0.5 // 为了方便计算,beforeDis[deep] == undefined的时候表示现在准备计算的是这层的第一个结点 // 因此设置最低上边缘为-0.5 if(beforeDis[deep] == undefined)beforeDis[deep] = -0.5; // 父边缘同意的进行初始化 if(beforeDis[deep - 1] == undefined)beforeDis[deep - 1] = -0.5; // 添加的新结点top值第一种求法:本层上边缘+1(比如上边缘是-0.5,那么top最小是top=-0.5+1=0.5) alltreedata[pNode.id].top = beforeDis[deep] + 1;var pTop=beforeDis[deep] + 1 + (alltreedata[pNode.pid].children.length - 1) * 0.5; // 计算的原则是:如果第一种可行,选择第一种,否则必须选择第二种 // 判断第一种是否可行的方法就是:如果第一种计算后确定的孩子上边缘不对导致孩子和孩子的前兄弟重合就是可行的 if(pTop - 1 < beforeDis[deep - 1]) // 必须保证父亲结点和父亲的前一个兄弟保存1的距离,至少 // 添加的新结点top值的第二种求法:根据孩子取孩子结点的中心top alltreedata[pNode.id].top = beforeDis[deep - 1] + 1 - (alltreedata[pNode.pid].children.length - 1) * 0.5;}else { // 此刻flag!=0 // 意味着结点有孩子,那么问题就解决了,直接取孩子的中间即可 // 其实,flag==0的分支计算的就是孩子,是没有孩子的叶结点,那是关键 alltreedata[pNode.id].top = (alltreedata[pNode.children[0]].top + alltreedata[pNode.children[flag - 1]].top) * 0.5;} // 因为计算孩子的时候 // 无法掌握父辈兄弟的情况 // 可能会出现父亲和兄弟重叠问题 if(alltreedata[pNode.id].top <= beforeDis[deep]){var needUp=beforeDis[deep] + 1 - alltreedata[pNode.id].top;(function doUp(_pid,_deep){alltreedata[_pid].top += needUp;if(beforeDis[_deep] < alltreedata[_pid].top)beforeDis[_deep] = alltreedata[_pid].top;var _flag;for(_flag = 0;_flag < alltreedata[_pid].children.length;_flag++) {doUp(alltreedata[_pid].children[_flag],_deep + 1);}})(pNode.id,deep);} // 计算好一个结点后,需要更新此刻该层的上边缘 beforeDis[deep] = alltreedata[pNode.id].top; // size在每次计算一个结点后更新,是为了最终绘图的时候知道树有多宽(此处应该叫高) if(alltreedata[pNode.id].top + 0.5 > size)size = alltreedata[pNode.id].top + 0.5;})(alltreedata[rootid],0); // 传递的参数分别表示:记录了位置信息的树结点集合、根结点ID和树的宽 return {"node":alltreedata,"root":rootid,"size":size,"deep":maxDeep + 1};}; /** * 根据配置的层次关系(配置的id,child,root)把原始数据变成内部结构,方便后期位置计算 * @param {any} initTree * * tempTree[id]={ * "data":原始数据, * "pid":父亲ID, * "id":唯一标识ID, * "children":[cid1、cid2、...] * } */var toInnerTree=function toInnerTree(initTree){var tempTree={}; // 根结点 var temp=config.root(initTree),id,rid;id = rid = config.id(temp);tempTree[id] = {"data":temp,"pid":null,"id":id,"children":[]};var num=1; // 根据传递的原始数据,生成内部统一结构 (function createTree(pdata,pid){var children=config.child(pdata,initTree),flag;num += children?children.length:0;for(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,"children":[]};createTree(children[flag],id);}})(temp,id);return {value:[rid,tempTree],num:num};}; // 可以传递任意格式的树原始数据 // 只要配置对应的解析方法即可 var tree=function tree(initTree){var treeData=toInnerTree(initTree);alltreedata = treeData.value[1];rootid = treeData.value[0];if(treeData.num == 1){alltreedata[rootid].left = 0.5;alltreedata[rootid].top = 0.5;return {deep:1,node:alltreedata,root:rootid,size:1};}return update();}; // 获取根结点的方法:root(initTree) tree.root = function(rootback){config.root = rootback;return tree;}; // 获取子结点的方法:child(parentTree,initTree) tree.child = function(childback){config.child = childback;return tree;}; // 获取结点ID方法:id(treedata) tree.id = function(idback){config.id = idback;return tree;};return tree;} /** * 点(x,y)围绕中心(cx,cy)旋转deg度 */var _rotate2=function _rotate2(cx,cy,deg,x,y){var cos=Math.cos(deg),sin=Math.sin(deg);return [+((x - cx) * cos - (y - cy) * sin + cx).toFixed(7),+((x - cx) * sin + (y - cy) * cos + cy).toFixed(7)];}; /** * 点(x,y)沿着向量(ax,ay)方向移动距离d */var _move2=function _move2(ax,ay,d,x,y){var sqrt=Math.sqrt(ax * ax + ay * ay);return [+(ax * d / sqrt + x).toFixed(7),+(ay * d / sqrt + y).toFixed(7)];}; /** * 点(x,y)围绕中心(cx,cy)缩放times倍 */var _scale2=function _scale2(cx,cy,times,x,y){return [+(times * (x - cx) + cx).toFixed(7),+(times * (y - cy) + cy).toFixed(7)];};var dot=function dot(config){config = initConfig$1({ // 前进方向 d:[1,1], // 中心坐标 c:[0,0], // 当前位置 p:[0,0]},config);var dotObj={ // 前进方向以当前位置为中心,旋转deg度 "rotate":function rotate(deg){var dPx=config.d[0] + config.p[0],dPy=config.d[1] + config.p[1];var dP=_rotate2(config.p[0],config.p[1],deg,dPx,dPy);config.d = [dP[0] - config.p[0],dP[1] - config.p[1]];return dotObj;}, // 沿着当前前进方向前进d "move":function move(d){config.p = _move2(config.d[0],config.d[1],d,config.p[0],config.p[1]);return dotObj;}, // 围绕中心坐标缩放 "scale":function scale(times){config.p = _scale2(config.c[0],config.c[1],times,config.p[0],config.p[1]);return dotObj;}, // 当前位置 "value":function value(){return config.p;}};return dotObj;};function treeLayout(config){config = initConfig$1({ // 类型:如果不是下面五种之一,就认为是原始类型 // type:LR|RL|BT|TB|circle // 如果类型是LR|RL|BT|TB需要设置如下参数 // width,height:宽和高 // 如果类型是circle需要设置如下参数 // 1.cx,cy:圆心;2.radius:半径;3.begin-deg,deg:开始和跨越弧度(可选) "begin-deg":0,"deg":Math.PI * 2},config);var treeCalc=treeLayout$1() // 配置数据格式 .root(config.root).child(config.child).id(config.id);var treeObj=function treeObj(initData){ // 计算初始坐标 var orgData=treeCalc(initData); // 计算deep for(var key in orgData.node) {orgData.node[key].deep = orgData.node[key].left - 0.5;}if(config.type === 'LR' || config.type === 'RL'){ // 每层间隔 var dis1=config.width / orgData.deep;if("RL" === config.type)dis1 *= -1; // 兄弟间隔 var dis2=config.height / (orgData.size - -0.5);for(var i in orgData.node) {var node=orgData.node[i];orgData.node[i].left = +(("RL" == config.type?config.width:0) - -node.left * dis1).toFixed(7);orgData.node[i].top = +(node.top * dis2).toFixed(7);}}else if(config.type === 'TB' || config.type === 'BT'){ // 每层间隔 var dis1=config.height / orgData.deep;if("BT" == config.type)dis1 *= -1; // 兄弟间隔 var dis2=config.width / (orgData.size - -0.5);var _left=undefined,_top=undefined;for(var i in orgData.node) {var node=orgData.node[i];_left = node.left;_top = node.top;orgData.node[i].top = +(("BT" == config.type?config.height:0) - -_left * dis1).toFixed(7);orgData.node[i].left = +(_top * dis2).toFixed(7);}}else if(config.type === 'circle'){ // 如果只有一个结点 if(orgData.deep == 1 && orgData.size == 1){orgData.node[orgData.root].left = config.cx;orgData.node[orgData.root].top = config.cy;} // 如果有多个结点 else { // 每层间距 var dis1=config.radius / (orgData.deep - 1); // 兄弟间隔弧度 var dis2=config.deg / (orgData.size - -0.5);for(var i in orgData.node) {var node=orgData.node[i];orgData.node[i].deg = (config['begin-deg'] - -dis2 * node.top) % (Math.PI * 2);var pos=_rotate2(config.cx,config.cy,orgData.node[i].deg,config.cx - -dis1 * (node.left - 0.5),config.cy);orgData.node[i].left = +pos[0];orgData.node[i].top = +pos[1];}}} // 启动绘图 if(isFunction(config.drawer)){ // 如果配置了绘图方法,就调用绘图方法 config.drawer(orgData);return treeObj;}else { // 否则返回数据 return orgData;}}; // 配置 treeObj.config = function(_config){config = initConfig$1(config,_config);return treeObj;}; // 设置绘图方法 treeObj.drawer = function(drawerback){config.drawer = drawerback;return treeObj;};return treeObj;}function pieLayout(config){config = initConfig$1({ // 饼图的开始和跨域角度[可选] "begin-deg":-Math.PI / 2,"deg":Math.PI * 2, // 饼图中一个瓣的中心参考半径,可以有多个[可选] "radius":[]}, // "cx": "", // "cy": "", // 设置数据结构[必选] // "value": function (data, key, index) { } config);if(!isFunction(config.value)){throw new Error('config.value must be a function!');}var pieObj=function pieObj(initData){var i=0,innerData=[],allData=0;for(var key in initData) {innerData.push({"value":config.value(initData[key],key,i),"data":initData[key],"key":key,"index":i,"dots":[]});allData += innerData[i].value;i += 1;}for(i = 0;i < innerData.length;i++) { // 起始弧度 innerData[i].beginDeg = i === 0?config['begin-deg']:innerData[i - 1].beginDeg + innerData[i - 1].deg; // 百分比 var percent=innerData[i].value / allData; // 跨越弧度 innerData[i].deg = percent * config.deg;innerData[i].percent = new Number(percent * 100).toFixed(2);} // 中心点(用于辅助绘制折线) if(isNumber(config.cx) && isNumber(config.cy)){for(i = 0;i < config.radius.length;i++) {for(var j=0;j < innerData.length;j++) {innerData[j].dots.push(_rotate2(config.cx,config.cy,innerData[j].beginDeg + innerData[j].deg * 0.5,config.cx + config.radius[i],config.cy));}}} // 启动绘图 if(isFunction(config.drawer)){config.drawer(innerData);return pieObj;}else {return innerData;}}; // 配置 pieObj.config = function(_config){config = initConfig$1(config,_config);return pieObj;}; // 设置绘图方法 pieObj.drawer = function(drawerback){config.drawer = drawerback;return pieObj;};return pieObj;} /** * 在(a,b,c)方向位移d */function _move(d,a,b,c){c = c || 0;var sqrt=Math.sqrt(a * a + b * b + c * c);return [1,0,0,0,0,1,0,0,0,0,1,0,a * d / sqrt,b * d / sqrt,c * d / sqrt,1];} /** * 围绕0Z轴旋转 * 其它的旋转可以借助transform实现 * 旋转角度单位采用弧度制 */function _rotate(deg){var sin=Math.sin(deg),cos=Math.cos(deg);return [cos,sin,0,0,-sin,cos,0,0,0,0,1,0,0,0,0,1];} /** * 围绕圆心x、y和z分别缩放xTimes, yTimes和zTimes倍 */function _scale(xTimes,yTimes,zTimes,cx,cy,cz){cx = cx || 0;cy = cy || 0;cz = cz || 0;return [xTimes,0,0,0,0,yTimes,0,0,0,0,zTimes,0,cx - cx * xTimes,cy - cy * yTimes,cz - cz * zTimes,1];} /** * 针对任意射线(a1,b1,c1)->(a2,b2,c2) * 计算出二个变换矩阵 * 分别为:任意射线变成OZ轴变换矩阵 + OZ轴变回原来的射线的变换矩阵 */function _transform(a1,b1,c1,a2,b2,c2){if(typeof a1 === 'number' && typeof b1 === 'number'){ // 如果设置二个点 // 表示二维上围绕某个点旋转 if(typeof c1 !== 'number'){c1 = 0;a2 = a1;b2 = b1;c2 = 1;} // 只设置三个点(设置不足六个点都认为只设置了三个点) // 表示围绕从原点出发的射线旋转 else if(typeof a2 !== 'number' || typeof b2 !== 'number' || typeof c2 !== 'number'){a2 = a1;b2 = b1;c2 = c1;a1 = 0;b1 = 0;c1 = 0;}if(a1 == a2 && b1 == b2 && c1 == c2)throw new Error('It\'s not a legitimate ray!');var sqrt1=Math.sqrt((a2 - a1) * (a2 - a1) + (b2 - b1) * (b2 - b1)),cos1=sqrt1 != 0?(b2 - b1) / sqrt1:1,sin1=sqrt1 != 0?(a2 - a1) / sqrt1:0,b=(a2 - a1) * sin1 + (b2 - b1) * cos1,c=c2 - c1,sqrt2=Math.sqrt(b * b + c * c),cos2=sqrt2 != 0?c / sqrt2:1,sin2=sqrt2 != 0?b / sqrt2:0;return [ // 任意射线变成OZ轴变换矩阵 [cos1,cos2 * sin1,sin1 * sin2,0,-sin1,cos1 * cos2,cos1 * sin2,0,0,-sin2,cos2,0,b1 * sin1 - a1 * cos1,c1 * sin2 - a1 * sin1 * cos2 - b1 * cos1 * cos2,-a1 * sin1 * sin2 - b1 * cos1 * sin2 - c1 * cos2,1], // OZ轴变回原来的射线的变换矩阵 [cos1,-sin1,0,0,cos2 * sin1,cos2 * cos1,-sin2,0,sin1 * sin2,cos1 * sin2,cos2,0,a1,b1,c1,1]];}else {throw new Error('a1 and b1 is required!');}} // 二个4x4矩阵相乘 // 或矩阵和齐次坐标相乘 var _multiply=function _multiply(matrix4,param){var newParam=[];for(var i=0;i < 4;i++) for(var j=0;j < param.length / 4;j++) newParam[j * 4 + i] = matrix4[i] * param[j * 4] + matrix4[i + 4] * param[j * 4 + 1] + matrix4[i + 8] * param[j * 4 + 2] + matrix4[i + 12] * param[j * 4 + 3];return newParam;}; /*! * 💡 - 列主序存储的4x4矩阵 * https://github.com/hai2007/tool.js/blob/master/Matrix4.js * * author hai2007 < https://hai2007.gitee.io/sweethome > * * Copyright (c) 2020-present hai2007 走一步,再走一步。 * Released under the MIT license */function Matrix4(initMatrix4){var matrix4=initMatrix4 || [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1];var matrix4Obj={ // 移动 "move":function move(dis,a,b,c){matrix4 = _multiply(_move(dis,a,b,c),matrix4);return matrix4Obj;}, // 旋转 "rotate":function rotate(deg,a1,b1,c1,a2,b2,c2){var matrix4s=_transform(a1,b1,c1,a2,b2,c2);matrix4 = _multiply(_multiply(_multiply(matrix4s[1],_rotate(deg)),matrix4s[0]),matrix4);return matrix4Obj;}, // 缩放 "scale":function scale(xTimes,yTimes,zTimes,cx,cy,cz){matrix4 = _multiply(_scale(xTimes,yTimes,zTimes,cx,cy,cz),matrix4);return matrix4Obj;}, // 乘法 // 可以传入一个矩阵(matrix4,flag) "multiply":function multiply(newMatrix4,flag){matrix4 = flag?_multiply(matrix4,newMatrix4):_multiply(newMatrix4,matrix4);return matrix4Obj;}, // 对一个坐标应用变换 // 齐次坐标(x,y,z,w) "use":function use(x,y,z,w){ // w为0表示点位于无穷远处,忽略 z = z || 0;w = w || 1;var temp=_multiply(matrix4,[x,y,z,w]);temp[0] = +temp[0].toFixed(7);temp[1] = +temp[1].toFixed(7);temp[2] = +temp[2].toFixed(7);temp[3] = +temp[3].toFixed(7);return temp;}, // 矩阵的值 "value":function value(){return matrix4;}};return matrix4Obj;} //当前正在运动的动画的tick函数堆栈 var $timers=[]; //唯一定时器的定时间隔 var $interval=13; //指定了动画时长duration默认值 var $speeds=400; //定时器ID var $timerId=null; /*! * 💡 - 动画轮播 * https://github.com/hai2007/tool.js/blob/master/animation.js * * author hai2007 < https://hai2007.gitee.io/sweethome > * * Copyright (c) 2020-present hai2007 走一步,再走一步。 * Released under the MIT license */ /** * @param {function} doback 轮询函数,有一个形参deep,0-1,表示执行进度 * @param {number} duration 动画时长,可选 * @param {function} callback 动画结束回调,可选,有一个形参deep,0-1,表示执行进度 * * @returns {function} 返回一个函数,调用该函数,可以提前结束动画 */function animation$1(doback,duration,callback){ // 如果没有传递时间,使用内置默认值 if(arguments.length < 2)duration = $speeds;var clock={ //把tick函数推入堆栈 "timer":function timer(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 start(){if(!$timerId){$timerId = setInterval(clock.tick,$interval);}}, //被定时器调用,遍历timers堆栈 "tick":function tick(){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() - createTime) / duration;passTime = passTime > 1?1:passTime;tick(passTime);if(passTime < 1 && timer.id){ //动画没有结束再添加 $timers.push(timer);}else if(callback){callback(passTime);}}if($timers.length <= 0){clock.stop();}}, //停止定时器,重置timerId=null "stop":function stop(){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 = undefined;return;}}};} /** * 初始化配置文件 * * @param {Json} init 默认值 * @param {Json} data * @return {Json} */function initConfig(init,data){for(var key in data) try{init[key] = data[key];}catch(e) {throw new Error("Illegal property value!");}return init;} /*! * 💡 - Hermite三次插值 * https://github.com/hai2007/tool.js/blob/master/Hermite.js * * author hai2007 < https://hai2007.gitee.io/sweethome > * * Copyright (c) 2020-present hai2007 走一步,再走一步。 * Released under the MIT license */function hermite(config){config = initConfig({ // 张弛系数 "u":0.5},config);var MR,a,b; /** * 根据x值返回y值 * @param {Number} x */var hermite=function hermite(x){if(MR){var sx=(x - a) / (b - a),sx2=sx * sx,sx3=sx * sx2;var sResult=sx3 * MR[0] + sx2 * MR[1] + sx * MR[2] + MR[3];return sResult * (b - a);}else throw new Error('You shoud first set the position!');}; /** * 设置点的位置 * @param {Number} x1 左边点的位置 * @param {Number} y1 * @param {Number} x2 右边点的位置 * @param {Number} y2 * @param {Number} s1 二个点的斜率 * @param {Number} s2 */hermite.setP = function(x1,y1,x2,y2,s1,s2){if(x1 < x2){ // 记录原始尺寸 a = x1;b = x2;var p3=config.u * s1,p4=config.u * s2; // 缩放到[0,1]定义域 y1 /= x2 - x1;y2 /= x2 - x1; // MR是提前计算好的多项式通解矩阵 // 为了加速计算 // 如上面说的 // 统一在[0,1]上计算后再通过缩放和移动恢复 // 避免了动态求解矩阵的麻烦 MR = [2 * y1 - 2 * y2 + p3 + p4,3 * y2 - 3 * y1 - 2 * p3 - p4,p3,y1];}else throw new Error('The point x-position should be increamented!');return hermite;};return hermite;} /** * 轮询动画 * @param {function} doback 轮询触发方法 * @param {number} time 动画时长,可选 * @param {function} callback 动画结束回调,可选 * @param {array|string} timing 动画进度控制参数,可选 * * @return {function} stop函数,可以提前停止动画 */function animation(doback,time,callback,timing){if(!isFunction(callback)){timing = callback;callback = false;} // 获取插值计算参数 var transition_timing=({"ease":[0.25,0.1,0.5,1],"ease-in":[0.5,0.0,0.75,0.6],"ease-in-out":[0.43,0.01,0.58,1],"ease-out":[0.25,0.6,0.5,1],"linear":"default"})[timing] || timing;var transition_timing_function=function transition_timing_function(deep){return deep;};if(transition_timing && isArray(transition_timing) && transition_timing.length == 4){transition_timing_function = hermite({"u":1}).setP(0,0,1,1,transition_timing[1] / transition_timing[0],(1 - transition_timing[3]) / (1 - transition_timing[2]));}return animation$1(function(deep){doback(transition_timing_function(deep));},time,function(deep){if(isFunction(callback)){if(deep != 1)deep = transition_timing_function(deep);callback(deep);}});} /** * Cardinal三次插值 * ---------------------------- * Hermite拟合的计算是,确定两个点和两个点的斜率 * 用一个y=ax(3)+bx(2)+cx+d的三次多项式来求解 * 而Cardinal是建立在此基础上 * 给定需要拟合的两个点和第一个点的前一个点+最后一个点的后一个点 * 第一个点的斜率由第一个点的前一个点和第二个点的斜率确定 * 第二个点的斜率由第一个点和第二个点的后一个点的斜率确定 */function cardinal(config){config = initConfig$1({ // 该参数用于调整曲线走势,默认数值t=0,分水岭t=-1,|t-(-1)|的值越大,曲线走势调整的越严重 "t":0},config);var HS=undefined,i=undefined; // 根据x值返回y值 var cardinal=function cardinal(x){if(HS){i = -1; // 寻找记录x实在位置的区间 // 这里就是寻找对应的拟合函数 while(i + 1 < HS.x.length && (x > HS.x[i + 1] || i == -1 && x >= HS.x[i + 1])) {i += 1;}if(i == -1 || i >= HS.h.length)throw new Error('Coordinate crossing!');return HS.h[i](x);}else {throw new Error('You shoud first set the position!');}}; // 设置张弛系数【应该在点的位置设置前设置】 cardinal.setT = function(t){if(typeof t === 'number'){config.t = t;}else {throw new Error('Expecting a figure!');}return cardinal;}; // 设置点的位置 // 参数格式:[[x,y],[x,y],...] // 至少两个点 cardinal.setP = function(points){HS = {"x":[],"h":[]};var flag=undefined,slope=(points[1][1] - points[0][1]) / (points[1][0] - points[0][0]),temp=undefined;HS.x[0] = points[0][0];for(flag = 1;flag < points.length;flag++) {if(points[flag][0] <= points[flag - 1][0])throw new Error('The point position should be increamented!');HS.x[flag] = points[flag][0]; // 求点斜率 temp = flag < points.length - 1?(points[flag + 1][1] - points[flag - 1][1]) / (points[flag + 1][0] - points[flag - 1][0]):(points[flag][1] - points[flag - 1][1]) / (points[flag][0] - points[flag - 1][0]); // 求解两个点之间的拟合方程 // 第一个点的前一个点直接取第一个点 // 最后一个点的后一个点直接取最后一个点 HS.h[flag - 1] = hermite({"u":(1 - config.t) * 0.5}).setP(points[flag - 1][0],points[flag - 1][1],points[flag][0],points[flag][1],slope,temp);slope = temp;}return cardinal;};return cardinal;} /** * 返回渲染后的CSS样式值 * @param {DOM} dom 目标结点 * @param {String} name 属性名称(可选) * @return {String} */function getStyle(dom,name){ // 获取结点的全部样式 var allStyle=document.defaultView && document.defaultView.getComputedStyle?document.defaultView.getComputedStyle(dom,null):dom.currentStyle; // 如果没有指定属性名称,返回全部样式 return isString(name)?allStyle.getPropertyValue(name):allStyle;} // 把颜色统一转变成rgba(x,x,x,x)格式 // 返回数字数组[r,g,b,a] var formatColor=function formatColor(color){var colorNode=document.getElementsByTagName('head')[0];colorNode.style['color'] = color;var rgba=getStyle(colorNode,'color');var rgbaArray=rgba.replace(/^rgba?\(([^)]+)\)$/,'$1').split(new RegExp('\\,' + REGEXP.whitespace));return [+rgbaArray[0],+rgbaArray[1],+rgbaArray[2],rgbaArray[3] == undefined?1:+rgbaArray[3]];}; // 获取一组随机色彩 var getRandomColors=function getRandomColors(num,alpha){if(!(alpha && alpha >= 0 && alpha <= 1))alpha = 1;var temp=[];for(var flag=1;flag <= num;flag++) {temp.push('rgba(' + (Math.random(1) * 230 + 20).toFixed(0) + ',' + (Math.random(1) * 230 + 20).toFixed(0) + ',' + (Math.random(1) * 230 + 20).toFixed(0) + ',' + alpha + ')');}return temp;}; // 获取一组循环色彩 var getLoopColors=function getLoopColors(num,alpha){if(!(alpha && alpha >= 0 && alpha <= 1))alpha = 1; // 颜色集合 var colorList=['rgba(84,112,198,' + alpha + ")",'rgba(145,204,117,' + alpha + ")",'rgba(250,200,88,' + alpha + ")",'rgba(238,102,102,' + alpha + ")",'rgba(115,192,222,' + alpha + ")",'rgba(59,162,114,' + alpha + ")",'rgba(252,132,82,' + alpha + ")",'rgba(154,96,180,' + alpha + ")",'rgba(234,124,204,' + alpha + ")"];var colors=[]; // 根据情况返回颜色数组 if(num <= colorList.length){ // 这种情况就不需要任何处理 return colorList;}else { // 如果正好是集合长度的倍数 if(num % colorList.length == 0){ // 将颜色数组循环加入后再返回 for(var i=0;i < num / colorList.length;i++) {colors = colors.concat(colorList);}}else {for(var j=1;j < num / colorList.length;j++) {colors = colors.concat(colorList);} // 防止最后一个颜色和第一个颜色重复 if(num % colorList.length == 1){colors = colors.concat(colorList[4]);}else {for(var k=0;k < num % colorList.length;k++) {colors = colors.concat(colorList[k]);}}}} // 返回结果 return colors;}; /** * 绑定事件 * @param {string} eventType * @param {function} callback */var bind=function bind(eventType,callback){if(window.attachEvent){for(var flag=0;flag < this.length;flag++) {this[flag].attachEvent("on" + eventType,callback);} // 后绑定的先执行 }else {for(var flag=0;flag < this.length;flag++) {this[flag].addEventListener(eventType,callback,false);} // 捕获 }return this;}; /** * 解除绑定事件 * @param {string} eventType * @param {function} handler */var unbind=function unbind(eventType,handler){if(window.detachEvent){for(var flag=0;flag < this.length;flag++) {this[flag].detachEvent("on" + eventType,handler);}}else {for(var flag=0;flag < this.length;flag++) {this[flag].removeEventListener(eventType,handler,false);}}return this;}; /** * 获取鼠标相对特定元素左上角位置 * @param {Event} event */var position=function position(event){ // 返回元素的大小及其相对于视口的位置 var bounding=this[0].getBoundingClientRect();if(!event || !event.clientX)throw new Error('Event is necessary!');return { // 鼠标相对元素位置 = 鼠标相对窗口坐标 - 元素相对窗口坐标 "x":event.clientX - bounding.left,"y":event.clientY - bounding.top};}; /** * 阻止冒泡 * @param {Event} event */var stopPropagation=function stopPropagation(event){event = event || window.event;if(event.stopPropagation){ //这是其他非IE浏览器 event.stopPropagation();}else {event.cancelBubble = true;}return this;}; /** * 阻止默认事件 * @param {Event} event */var preventDefault=function preventDefault(event){event = event || window.event;if(event.preventDefault){event.preventDefault();}else {event.returnValue = false;}return this;}; /* 等角斜方位投影 */var // 围绕X轴旋转 _rotateX=function _rotateX(deg,x,y,z){var cos=Math.cos(deg),sin=Math.sin(deg);return [x,y * cos - z * sin,y * sin + z * cos];}, // 围绕Y轴旋转 _rotateY=function _rotateY(deg,x,y,z){var cos=Math.cos(deg),sin=Math.sin(deg);return [z * sin + x * cos,y,z * cos - x * sin];}, // 围绕Z轴旋转 _rotateZ=function _rotateZ(deg,x,y,z){var cos=Math.cos(deg),sin=Math.sin(deg);return [x * cos - y * sin,x * sin + y * cos,z];};var p=[];function eoap(config,longitude,latitude){ /** * 通过旋转的方法 * 先旋转出点的位置 * 然后根据把地心到旋转中心的这条射线变成OZ这条射线的变换应用到初始化点上 * 这样求的的点的x,y就是最终结果 * * 计算过程: * 1.初始化点的位置是p(x,0,0),其中x的值是地球半径除以缩放倍速 * 2.根据点的纬度对p进行旋转,旋转后得到的p的坐标纬度就是目标纬度 * 3.同样的对此刻的p进行经度的旋转,这样就获取了极点作为中心点的坐标 * 4.接着想象一下为了让旋转中心移动到极点需要进行旋转的经纬度是多少,记为lo和la * 5.然后再对p进行经度度旋转lo获得新的p * 6.然后再对p进行纬度旋转la获得新的p * 7.旋转结束 * * 特别注意:第5和第6步顺序一定不可以调换,原因来自经纬度定义上 * 【除了经度为0的位置,不然纬度的旋转会改变原来的经度值,反过来不会】 * */p = _rotateY((360 - latitude) / 180 * Math.PI,100 * config.scale,0,0);p = _rotateZ(longitude / 180 * Math.PI,p[0],p[1],p[2]);p = _rotateZ((90 - config.center[0]) / 180 * Math.PI,p[0],p[1],p[2]);p = _rotateX((90 - config.center[1]) / 180 * Math.PI,p[0],p[1],p[2]);return [-p[0], //加-号是因为浏览器坐标和地图不一样 p[1],p[2]];}function map(_config){var config=initConfig$1({ // 默认使用「等角斜方位投影」 type:'eoap', // 缩放比例 scale:1, // 投影中心经纬度 center:[107,36]},_config);var map=function map(longitude,latitude){switch(config.type){case 'eoap':{return eoap(config,longitude,latitude);}default:{throw new Error('Map type configuration error!');}}}; // 修改配置 map.config = function(_config){config = initConfig$1(config,_config);return map;};return map;} /*! * 💡 - 刻度尺刻度求解 * https://github.com/hai2007/tool.js/blob/master/ruler.js * * author hai2007 < https://hai2007.gitee.io/sweethome > * * Copyright (c) 2021-present hai2007 走一步,再走一步。 * Released under the MIT license */ // 需要注意的是,实际的间距个数可能是 num-1 或 num 或 num+1 或 1 function ruler$1(maxValue,minValue,num){ // 如果最大值最小值反了 if(maxValue < minValue){var temp=minValue;minValue = maxValue;maxValue = temp;} // 如果相等 else if(maxValue == minValue){return [maxValue];} // 计算最终小数点应该保留的位数 var dotMaxNum=(maxValue + ".").split('.')[1].length;var dotMinNum=(minValue + ".").split('.')[1].length;var dotNum=dotMaxNum > dotMinNum?dotMaxNum:dotMinNum; // 为了变成 -100 ~ 100 需要放大或者缩小的倍数 var times100=(function(_value){ // 先确定基调,是放大还是缩小 var _times100_base=_value < 100 && _value > -100?10:0.1; // 记录当前缩放倍数 var _times100=1,_tiemsValue=_value;while(_times100_base == 10? // 如果是放大,超过 -100 ~ 100 就应该停止 _tiemsValue >= -100 && _tiemsValue <= 100: // 如果是缩小,进入 -100 ~ 100 就应该停止 _tiemsValue <= -100 || _tiemsValue >= 100) {_times100 *= _times100_base;_tiemsValue *= _times100_base;}return _times100;})( // 根据差值来缩放 maxValue - minValue); // 求解出 -100 ~ 100 的最佳间距值 后直接转换原来的倍数 var distance=+(Math.ceil((maxValue - minValue) * times100 / num) / times100).toFixed(dotNum);if(distance <= 0)return [minValue,maxValue]; // 最小值,也就是起点 var begin=Math.floor(minValue / distance) * distance;var rulerArray=[],index; // 获取最终的刻度尺数组 rulerArray.push(begin);for(index = 1;rulerArray[rulerArray.length - 1] < maxValue;index++) {rulerArray.push(+(begin + distance * index).toFixed(dotNum));}rulerArray[0] = +begin.toFixed(dotNum); // 最后,进行校对 var _rulerArray=[rulerArray[0]];for(var i=1;i < rulerArray.length;i++) {if(_rulerArray[_rulerArray.length - 1] != rulerArray[i]){_rulerArray.push(rulerArray[i]);}}return _rulerArray;} // 刻度计算 function ruler(maxValue,minValue){var num=arguments.length <= 2 || arguments[2] === undefined?5:arguments[2];var rulerArray=ruler$1(maxValue,minValue,num);return {min:rulerArray[0],max:rulerArray[rulerArray.length - 1],distance:rulerArray.length <= 1?0:rulerArray[1] - rulerArray[0],num:rulerArray.length - 1,ruler:rulerArray};} /** * 把当前维护的结点加到目标结点内部的结尾 * @param {selector} target * @return {image2D} */var appendTo=function appendTo(target,context){var nodes=sizzle(target,context || document);if(nodes.length > 0){for(var i=0;i < this.length;i++) {nodes[0].appendChild(this[i]);}}else {throw new Error('Target empty!');}return this;}; /** * 把当前维护的结点加到目标结点内部的开头 * @param {selector} target * @return {image2D} */var prependTo=function prependTo(target,context){var nodes=sizzle(target,context || document);if(nodes.length > 0){for(var i=0;i < this.length;i++) {nodes[0].insertBefore(this[i],nodes[0].childNodes[0]);}}else {throw new Error('Target empty!');}return this;}; /** * 把当前维护的结点加到目标结点之后 * @param {selector} target * @return {image2D} */var afterTo=function afterTo(target,context){var nodes=sizzle(target,context || document);if(nodes.length > 0){for(var i=0;i < this.length;i++) { //如果第二个参数undefined,在结尾追加,目的一样达到 nodes[0].parentNode.insertBefore(this[i],nodes[0].nextSibling);}}else {throw new Error('Target empty!');}return this;}; /** * 把当前维护的结点加到目标结点之前 * @param {selector} target * @return {image2D} */var beforeTo=function beforeTo(target,context){var nodes=sizzle(target,context || document);if(nodes.length > 0){for(var i=0;i < this.length;i++) {nodes[0].parentNode.insertBefore(this[i],nodes[0]);}}else {throw new Error('Target empty!');}return this;}; // 删除当前维护的结点 var remove=function remove(){for(var i=0;i < this.length;i++) {this[i].parentNode.removeChild(this[i]);}return this;}; // 筛选当前结点 var filter=function filter(filterback){var temp=[];for(var i=0;i < this.length;i++) {if(filterback(i,image2D(this[i])))temp.push(this[i]);}return image2D(temp);}; // 修改文本或获取结点文本 var text=function text(content){if(arguments.length > 0){for(var i=0;i < this.length;i++) {this[i].textContent = content;}return this;}if(this.length <= 0)throw new Error('Target empty!');return this[0].textContent;}; // 设置或获取结点中的xhtml字符串模板(相当于innerHTML) var html=function html(xhtmlString){if(arguments.length > 0){for(var i=0;i < this.length;i++) { // 如果是SVG标签 if(/[a-z]/.test(this[i].tagName)){setSVG(this[i],xhtmlString);} // 否则是普通html标签 else {this[i].innerHTML = xhtmlString;}}return this;}if(this.length <= 0)throw new Error('Target empty!');return this[0].innerHTML;}; // 获取元素大小 var size=function size(type){if(this.length <= 0)throw new Error('Target empty!');var elemHeight=undefined,elemWidth=undefined,dom=this[0];if(type == 'content'){ //内容 elemWidth = dom.clientWidth - (getStyle(dom,'padding-left') + "").replace('px','') - (getStyle(dom,'padding-right') + "").replace('px','');elemHeight = dom.clientHeight - (getStyle(dom,'padding-top') + "").replace('px','') - (getStyle(dom,'padding-bottom') + "").replace('px','');}else if(type == 'padding'){ //内容+内边距 elemWidth = dom.clientWidth;elemHeight = dom.clientHeight;}else if(type == 'border'){ //内容+内边距+边框 elemWidth = dom.offsetWidth;elemHeight = dom.offsetHeight;}else if(type == 'scroll'){ //包含滚动的尺寸(不包括border) elemWidth = dom.scrollWidth;elemHeight = dom.scrollHeight;}else {elemWidth = dom.offsetWidth;elemHeight = dom.offsetHeight;}return {width:elemWidth,height:elemHeight};}; /** * 设置或获取样式 * @arguments(key):获取指定样式 * @arguments(key,value):设置指定样式 * @arguments():获取全部样式 * @arguments(json):设置大量样式 */function style(){ // 获取样式 if(arguments.length <= 1 && (arguments.length <= 0 || typeof arguments[0] !== 'object')){if(this.length <= 0)throw new Error('Target empty!'); // 为了获取非style定义的样式,需要使用特殊的方法获取 return getStyle(this[0],arguments[0]);} // 设置样式 for(var i=0;i < this.length;i++) {if(arguments.length === 1){for(var key in arguments[0]) {this[i].style[key] = arguments[0][key];}}else this[i].style[arguments[0]] = arguments[1];}return this;}var setAttribute=function setAttribute(dom,attr,val){if(/[a-z]/.test(dom.tagName) && XLINK_ATTRIBUTE.indexOf(attr) >= 0){ // 如果是xml元素 // 针对xlink使用特殊方法赋值 dom.setAttributeNS(NAMESPACE.xlink,'xlink:' + attr,val);}else dom.setAttribute(attr,val);}; /** * 设置或获取属性 * @arguments(attr):获取属性 * @arguments(attr,value):设置指定属性值 * @arguments(json):设置大量属性 */function attribute(){ // 获取属性值 if(arguments.length === 1 && typeof arguments[0] !== 'object'){if(this.length <= 0)throw new Error('Target empty!');return this[0].getAttribute(arguments[0]);} // 设置属性值 else if(arguments.length > 0){for(var i=0;i < this.length;i++) {if(arguments.length === 1){for(var key in arguments[0]) {setAttribute(this[i],key,arguments[0][key]);}}else setAttribute(this[i],arguments[0],arguments[1]);}}return this;} // 用于把数据绑定到一组结点或返回第一个结点数据 // 可以传递函数对数据处理 var datum=function datum(data,calcback){ // 获取数据 if(arguments.length <= 0){if(this.length <= 0)throw new Error('Target empty!');return this[0].__data__;} // 设置数据 for(var i=0;i < this.length;i++) {this[i].__data__ = typeof calcback === 'function'?calcback(data,i):data;}return this;}; // 用于把一组数据绑定到一组结点或返回一组结点数据 // 可以传递函数对数据处理 var data=function data(datas,calcback){ // 获取数据 if(arguments.length <= 0){var _temp=[];for(var _i=0;_i < this.length;_i++) {_temp[_i] = this[_i].__data__;}return _temp;} // 设置数据 var temp=[],i=undefined;for(i = 0;i < this.length && i < datas.length;i++) {this[i].__data__ = typeof calcback === 'function'?calcback(datas[i],i):datas[i];temp.push(this[i]);}var newImage2D=image2D(temp); // 记录需要去平衡的数据 newImage2D.__enter__ = [];for(;i < datas.length;i++) newImage2D.__enter__.push(typeof calcback === 'function'?calcback(datas[i],i):datas[i]); // 记录需要去平衡的结点 newImage2D.__exit__ = [];for(;i < this.length;i++) newImage2D.__exit__.push(this[i]);return newImage2D;}; // 把过滤出来多于结点的数据部分变成结点返回 // 需要传递一个字符串来标明新创建元素是什么 var enter=function enter(template,type){if(!this.__enter__ || this.__enter__.constructor !== Array)throw new Error('Not a data node object to be balanced!');var temp=[];for(var i=0;i < this.__enter__.length;i++) {temp[i] = toNode$1(template,type);temp[i].__data__ = this.__enter__[i];}delete this.__enter__;return image2D(temp);}; // 把过滤出来多于数据的结点部分返回 var exit=function exit(){if(!this.__exit__ || this.__exit__.constructor !== Array)throw new Error('Not a data node object to be balanced!');var exitImage2D=image2D(this.__exit__);delete this.__exit__;return exitImage2D;}; // 在维护的结点上轮询执行传入的方法 // doback(data,index,image2D) var loop=function loop(doback){for(var i=0;i < this.length;i++) {doback(this[i].__data__,i,image2D(this[i]));}return this;}; // r1和r2,内半径和外半径 // beginA起点弧度,rotateA旋转弧度式 function arc(beginA,rotateA,cx,cy,r1,r2,doback){ // 有了前置的判断,这里可以省略了 // if (rotateA > Math.PI * 2) rotateA = Math.PI * 2; // if (rotateA < -Math.PI * 2) rotateA = -Math.PI * 2; // 保证逆时针也是可以的 if(rotateA < 0){beginA += rotateA;rotateA *= -1;}var temp=[],p=undefined; // 内部 p = _rotate2(0,0,beginA,r1,0);temp[0] = p[0];temp[1] = p[1];p = _rotate2(0,0,rotateA,p[0],p[1]);temp[2] = p[0];temp[3] = p[1]; // 外部 p = _rotate2(0,0,beginA,r2,0);temp[4] = p[0];temp[5] = p[1];p = _rotate2(0,0,rotateA,p[0],p[1]);temp[6] = p[0];temp[7] = p[1];doback(beginA,beginA + rotateA,temp[0] + cx,temp[1] + cy,temp[4] + cx,temp[5] + cy,temp[2] + cx,temp[3] + cy,temp[6] + cx,temp[7] + cy,(r2 - r1) * 0.5);} // 文字统一设置方法 var initText$1=function initText$1(painter,config,x,y,deg){painter.beginPath();painter.translate(x,y);painter.rotate(deg);painter.font = config['font-size'] + "px " + config['font-family'];return painter;}; // 画弧统一设置方法 var initArc$1=function initArc$1(painter,config,cx,cy,r1,r2,beginDeg,deg){if(r1 > r2){var temp=r1;r1 = r2;r2 = temp;}beginDeg = beginDeg % (Math.PI * 2); // 当|deg|>=2π的时候都认为是一个圆环 // 为什么不取2π比较,是怕部分浏览器浮点不精确,同时也是为了和svg保持一致 if(deg >= Math.PI * 1.999999 || deg <= -Math.PI * 1.999999){deg = Math.PI * 2;}else {deg = deg % (Math.PI * 2);}arc(beginDeg,deg,cx,cy,r1,r2,function(beginA,endA,begInnerX,begInnerY,begOuterX,begOuterY,endInnerX,endInnerY,endOuterX,endOuterY,r){if(r < 0)r = -r;painter.beginPath();painter.moveTo(begInnerX,begInnerY);painter.arc( // (圆心x,圆心y,半径,开始角度,结束角度,true逆时针/false顺时针) cx,cy,r1,beginA,endA,false); // 结尾 if(config["arc-end-cap"] == 'round')painter.arc((endInnerX + endOuterX) * 0.5,(endInnerY + endOuterY) * 0.5,r,endA - Math.PI,endA,true);else if(config["arc-end-cap"] == '-round')painter.arc((endInnerX + endOuterX) * 0.5,(endInnerY + endOuterY) * 0.5,r,endA - Math.PI,endA,false);else painter.lineTo(endOuterX,endOuterY);painter.arc(cx,cy,r2,endA,beginA,true); // 开头 if(config["arc-start-cap"] == 'round')painter.arc((begInnerX + begOuterX) * 0.5,(begInnerY + begOuterY) * 0.5,r,beginA,beginA - Math.PI,true);else if(config["arc-start-cap"] == '-round')painter.arc((begInnerX + begOuterX) * 0.5,(begInnerY + begOuterY) * 0.5,r,beginA,beginA - Math.PI,false);else painter.lineTo(begInnerX,begInnerY);});if(config["arc-start-cap"] == 'butt')painter.closePath();return painter;}; // 画圆统一设置方法 var initCircle$1=function initCircle$1(painter,cx,cy,r){painter.beginPath();painter.moveTo(cx + r,cy);painter.arc(cx,cy,r,0,Math.PI * 2);return painter;}; // 画矩形统一设置方法 var initRect$1=function initRect$1(painter,x,y,width,height){painter.beginPath();painter.rect(x,y,width,height);return painter;}; // 线性渐变 var linearGradient$1=function linearGradient$1(painter,x0,y0,x1,y1){var gradient=painter.createLinearGradient(x0,y0,x1,y1);var enhanceGradient={"value":function value(){return gradient;},"addColorStop":function addColorStop(stop,color){gradient.addColorStop(stop,color);return enhanceGradient;}};return enhanceGradient;}; // 环形渐变 var radialGradient$1=function radialGradient$1(painter,cx,cy,r){var gradient=painter.createRadialGradient(cx,cy,0,cx,cy,r);var enhanceGradient={"value":function value(){return gradient;},"addColorStop":function addColorStop(stop,color){gradient.addColorStop(stop,color);return enhanceGradient;}};return enhanceGradient;}; // 加强版本的画笔 function painter_canvas2D(canvas,noHiddenWarn){ // 获取canvas2D画笔 var painter=canvas.getContext("2d");var isLayer=canvas.__image2D__layer__ == 'yes'; // 图层是内部的,明确获取方法 // 对外的一律使用clientXXX,区分是否显示 var width=isLayer?canvas.getAttribute('width'):canvas.clientWidth, //内容+内边距 height=isLayer?canvas.getAttribute('height'):canvas.clientHeight;if(width == 0 || height == 0){if(!noHiddenWarn)console.warn('Canvas is hidden or size is zero!');if(canvas.__image2D__noLayer_getSize__ == 'yes'){width = canvas.width / 2;height = canvas.height / 2;}else {width = canvas.width;height = canvas.height; // 标记已经特殊获取大小了 canvas.__image2D__no