UNPKG

jj-chart

Version:

copy mychart

1,556 lines (1,442 loc) 683 kB
/** * @mtfe/mchart - A JavaScript chart library for Canvas. * @date 2018/03/16 * @version v1.0.3 * @license ISC */ "use strict"; !(function(global){ var Graph = (function(){ var toString = Object.prototype.toString; var isObject = function(a) { return toString.call(a) === "[object Object]"; }; var isNumber = function(a, finite) { return typeof a === "number" && !isNaN(a) && (finite !== true || isFinite(a)); }; var isNumberIN = function(a) { return isNumber(a) && isFinite(a); }; var isArray = function(a) { return toString.call(a) === "[object Array]"; }; var isFunction = function(a) { return toString.call(a) === "[object Function]"; }; var isString = function(a) { return toString.call(a) === "[object String]"; }; var isEmptyObject = function(o) { for (var p in o) if (o.hasOwnProperty(p)) return false; return true; }; var defined = function(a) { return typeof a !== "undefined" && a !== null; }; var Graph = { DEVICE_PIXEL_RATIO: global.devicePixelRatio || 1, isArray: isArray, isFunction: isFunction, isObject: isObject, isString: isString, isNumber: isNumber, isNumberIN: isNumberIN, isEmptyObject: isEmptyObject, defined: defined }; /** * @param first is object type * @param last default value */ Graph.pack = function(){ var r = { "number": [0, isNumber], "function": [null, isFunction], "object": [null, isObject], "string": ["", isString], "array": [[], isArray] }; var params = arguments, //arraySlice.call(arguments, 0), type = params[0]; !isString(type) && (type = ""); var v, i, n = params.length; var t = r[type]; for(i = 1; i < n; i++){ v = params[i]; if(type && t && t[1] && t[1](v)){ return v; } } return t && t[0]; }; /* * merge b to a * @param a{Object} source object * @param b{Object} target object * Returns new object */ var extend = Graph.extend = function(){ var args = arguments,// arraySlice.call(arguments, 1), i = 1, length = args.length, a = args[0], b, p; if(!isObject(a) && !isFunction(a)){ a = {}; } for(; i < length; i++){ b = args[i]; for(p in b){ var src = a[p], copy = b[p]; if(src === copy) continue; if(copy && isObject(copy)){ a[p] = extend(src, copy); } else if(copy !== undefined){ a[p] = copy; } } } return a; }; Graph.Math = (function(){ var mathPow = Math.pow; var mathRound = Math.round; var mathLog = Math.log; function round(v, p) { p = mathPow(10, p || 0); return p === 0 ? v : mathRound(v * p) / p; } var Mathematics = { log: function(v, base, positive){ base = base || 10; typeof positive === "undefined" && (positive = true); return (!!positive ? mathLog(v < 0 ? 0 : v) : -mathLog(v > 0 ? 0 : -v)) / mathLog(base); }, pow: function(v, base, positive){ base = base || 10; typeof positive === "undefined" && (positive = true); return !!positive ? mathPow(base, v) : -mathPow(base, -v); }, round: round }; return Mathematics; }).call(typeof window !== "undefined" ? window : this);; Graph.Numeric = (function(){ var abs = Math.abs, log = Math.log, pow = Math.pow, round = Math.round; var isNumber = function(a){ return typeof a === "number" && a === a; }; /* * The array fill * @param value{Number} value * @param min{Number} min range * @param max{Number} max range * Returns is a number value */ function clamp(value, min, max){ return (value = value || 0) < min ? min : value > max ? max : value; } /* * linear calculation * @param value{Number} * @param minValue{Number} * @param maxValue{Number} * @param minRange{Number} * @param maxRange{Number} * Returns a linear value, f(y) = ax + b */ var interpolate = function(value, minValue, maxValue, minRange, maxRange){ var dissRange = maxRange - minRange,//定义域 dissDomain = maxValue - minValue,//值域 retValue; dissDomain = dissDomain ? 1 / dissDomain : 0;//fix value is 0 retValue = (value - minValue) * dissDomain; return minRange + dissRange * retValue;//ax + b }; var toPrecision = function(n, precision){ var EPSILON = 8;//0.00000001 if(arguments.length < 2) precision = EPSILON; return Number.prototype.toPrecision ? Number(n).toPrecision(precision) : (function(n, precision){ if(n === 0 || isNaN(n) || isFinite(n)) return "" + n; var ln10 = ~~(log(abs(n)) / Math.LN10);//log base var m; if(precision > ln10){ m = pow(10, precision - ln10 - 1); return "" + (m === 0 ? n : round(n * m) / m); } m = pow(10, precision - ln10 + 1); return "" + (m === 0 ? n : round(n / m) * m); })(n, precision); }; var Numeric = { clamp: clamp, percentage: function(value, percentage){ var rPercent = /^[+\-\s\.\d]+\s*%\s*$/; return isNumber(value) && rPercent.test(percentage) ? value * (parseFloat(percentage, 10) / 100) : NaN; }, interpolate: interpolate, toPrecision: toPrecision }; return Numeric; }).call(typeof global !== "undefined" ? global : this);; Graph.Vector = (function(){ var isNumber = function(a){ return typeof a === "number" && a === a; }; function pack(){ var r = { "number": [0, isNumber] }; var params = Array.prototype.slice.call(arguments, 0), type = params[0]; var v, i; for(i = 1; i < params.length; i++){ v = params[i]; if(type && r[type] && r[type][1] && r[type][1](v)){ return v; } } return r[type] && r[type][0]; } var Vector = function(){ return (arguments.length >= 3 ? new Vector3D(arguments[0], arguments[1], arguments[2]) : new Vector2D(arguments[0], arguments[1])); }; Vector.prototype = { add: function(v){ this.x += pack("number", v.x, 0); this.y += pack("number", v.y, 0); }, sub: function(v){ this.x -= pack("number", v.x, 0); this.y -= pack("number", v.y, 0); return this; }, length: function(){ return Math.sqrt(this.x * this.x + this.y * this.y); } }; function Vector2D(x, y){ if(!isNumber(x) || !isNumber(y)){ throw new Error("x and y not a number."); } this.x = x; this.y = y; return this; } Vector2D.prototype = Vector.prototype; Vector2D.prototype.horizontal = function(v){ var x1 = this.x, y1 = this.y; var x2 = v.x, y2 = v.y; //a//b x1*y2=y1*x2 //var c = x1 * y2 === x2 * y1; //x1x2+y1y2=0 //console.log(x1 * y2, x2 * y1, c, (y2 - y1) / (x2 - x1), x1*x2+y1*y2); var ax = x2 - x1; if(Math.abs(ax) <= 0.1){ return false; } return (y2 - y1) / (ax) <= 0.1; //return isNumber(c) ? c : false;// === 0; }; function Vector3D(x, y, z){ this.x = x; this.y = y; this.z = z; } if(typeof module === "object" && module.exports){ module.exports = Vector; } else if(typeof define === "function" && define.amd){ define(function(){ return Vector; }); } return Vector; }).call(typeof global !== "undefined" ? global : window);; Graph.Formatter = (function(){ //Think Right Before you Leap function TRouBLe() { var args = arguments, n = args.length; var top = args[0]; var filled = function(n, v) { var r = [], i = -1; while (++i < n) r.push(pack("number", v[i], 0)); return r; }; var fixed = function(d) { var r = [], n = d.length, i = -1; while (++i < n) r.push(pack("number", d[i], 0)); return r; }; var concated = function() { var args = [].slice.call(arguments, 0), i = -1, n = args.length; var r = []; while (++i < n) r = r.concat(args[i]); return r; }; if (!n && !(top = 0) || (isNumber(top, true) && n === 1)) { return [top, top, top, top]; } if (!isArray(args[0]) && n) { top = [].slice.call(args, 0); } n = top.length; return n === 1 ? filled(4, top.concat(top, top, top)) : n === 2 ? concated(filled(2, top), fixed(top)) : n === 3 ? concated(fixed(top), filled(1, [top[0]])) : fixed(top.slice(0, 4)); } var Formatter = { String: {}, TRouBLe: TRouBLe }; function factoy(Graph){ var defined = Graph.defined; var isNumber = Graph.isNumber; Formatter.String = { padding: function(v, p){ return v > -1 && v < 10 ? (p = p || "0") + v : v; }, numberFormat: function(v, sep, decimals){ var places = defined(decimals) ? (isNumber(decimals = Math.abs(decimals)) ? decimals : 2) : Math.min((v.toString().split(".")[1] || "").length, 20), negative = v < 0 ? "-" : ""; var words = String(parseInt(v = Math.abs(v).toFixed(places))), first = words.length > 3 ? words.length % 3 : 0, rSep = /(\d{3})(?=\d)/g; sep = typeof sep === "string" ? sep : ","; return !isNaN(+v) && isFinite(v) ? [ negative,//positive or negative first ? words.substr(0, first) + (sep) : "",//prefix words.substr(first).replace(rSep, "$1" + sep),//middle places ? "." + Math.abs(v - words).toFixed(places).slice(2) : ""//suffix ].join("") : ""; } }; return Formatter; } var exports = { deps: function(){ var args = Array.prototype.slice.call(arguments, 0); return factoy.apply(global, [].concat(args)); } }; if(typeof module === "object" && module.exports){ module.exports = exports; } else if(typeof define === "function" && define.amd){ define(function(){ return exports; }); } return exports; }).call(typeof global !== "undefined" ? global : this).deps(Graph); Graph.Heap = (function(){ /** * @param compare{Function} * @example * var heap = Heap(function(a, b){ return a - b; }); * heap.push(19); * heap.push(29); * heap.push(9); * for(var i = 0; i < 6; i++){ * heap.push(Math.random() * 20 | 0); * } * //var t = heap.pop(); * console.log(heap, heap.length); * for(i = 0; i < 4; i++) * console.log(heap.pop(), heap.length); */ var defaultCompare = function(a, b){ return a - b; }; function down(array, i){ var value = array[i]; var size = array.length; while(true){ var r = (i + 1) << 1, l = r - 1, j = i; var child = array[j]; if(l < size && defaultCompare(child, array[l]) > 0) child = array[j = l]; if(r < size && defaultCompare(child, array[r]) > 0) child = array[j = r]; if(j === i) break; array[i] = child; array[i = j] = value; } } function up(array, i){ var value = array[i]; while(i > 0){ var j = (i + 1 >> 1) - 1, parent = array[j]; if(defaultCompare(value, parent) >= 0) break; array[i] = parent; array[i = j] = value; } } var Heap = function(compare){ return new Heap.init(compare); }; Heap.init = function(compare){ defaultCompare = compare || defaultCompare; this.length = 0; return this; }; Heap.prototype = { push: function(value){ var size = this.length; this[size] = value; up(this, size++); return this.length = size; }, pop: function(){ var size = this.length; if(size <= 0) return null; var removed = this[0]; var end = this.splice(size - 1, 1)[0]; if((this.length = --size) > 0){ this[0] = end;//this[size]; down(this, 0); } return removed; }, peek: function(){ return this[0]; }, splice: [].splice, size: function(){ return this.length; }, empty: function(){ return this.length <= 0; } }; Heap.init.prototype = Heap.prototype; if(typeof module === "object" && module.exports){ module.exports = Heap; } else if(typeof define === "function" && define.amd){ define(function(){ return Heap; }); } return Heap; }).call(typeof window !== "undefined" ? window : this);; Graph.KDTree = (function(global){ var Tree = function(node, parent, dim){ this.node = node; this.left = null; this.right = null; this.parent = parent; this.dim = dim; }; function factoy(Heap){ var heap = new Heap(function(a, b){ return b.distance - a.distance; //return -a.distance; }); function buildTree(points, depth, parent, dimensions){ var length = points.length, d = depth % Math.max(1, dimensions.length), dim, sorted = function(a, b){ return a - b; }, m; var node; if(!length) return null; if(length === 1) return new Tree(points[0], parent, d);//root if(dimensions.length){ dim = dimensions[d];//dimensions size sorted = function(a, b){ return a[dim] - b[dim]; }; } points.sort(sorted); node = new Tree(points[m = points.length >> 1], parent, d); node.left = buildTree(points.slice(0, m), depth + 1, node, dimensions); node.right = buildTree(points.slice(m + 1), depth + 1, node, dimensions); return node; } var KDTree = function(points, dimensions){ return new KDTree.init(points.slice(0), dimensions); }; KDTree.init = function(points, dimensions){ this.build(points, dimensions); return this; }; KDTree.prototype = { build: function(points, dimensions){ this.dimensions = dimensions || ["x", "y"]; this.root = buildTree(points, 0, null, this.dimensions); }, nearest: function(point, callback, n){ var dimensions = this.dimensions, dl = dimensions.length; n = n || 1; //reset heap while(!heap.empty()){ heap.pop(); } function put(node, distance){ heap.push({ node: node, distance: distance }); if(heap.size() > n){ heap.pop(); } } function find(tree){ var maps = {}, aValue = callback(point, tree.node), bValue; var node; if(dl){ dimensions.forEach(function(item, i){ maps[item] = i === tree.dim ? point[item] : tree.node[item]; }); } else{ maps = point; } bValue = callback(maps, tree.node); //console.log(tree.node.x1, tree.node.x0, tree.node.y0, tree.node.y1) //parent if(tree.right === null && tree.left === null){ if(heap.size() < n || aValue < heap.peek().distance){ put(tree, aValue); } return null; } if(tree.right === null){ node = tree.left; } else if(tree.left === null){ node = tree.right; } //left && right else{ node = (dl ? point[dimensions[tree.dim]] < tree.node[dimensions[tree.dim]] : point < tree.node) ? tree.left : tree.right; } find(node); if(heap.size() < n || aValue < heap.peek().distance){ put(tree, aValue); } if(heap.size() < n || Math.abs(bValue) < heap.peek().distance){ var child = node === tree.left ? tree.right : tree.left; child !== null && find(child); } } this.root && find(this.root); var r = []; for(var i = 0; i < n; i++){ //console.log(heap) if(!heap.empty() && heap[i].node !== null) r.push(heap[i].node.node); } return r; }, //TODO insert: function(){ }, //TODO remove: function(){}, destroy: function(){ //destory /*while(heap.size()){ heap.pop(); } heap = null;*/ this.root = null; } }; KDTree.init.prototype = KDTree.prototype; return KDTree; } return { deps: function(){ var args = Array.prototype.slice.call(arguments, 0); return factoy.apply(global, [].concat(args)); } }; })(typeof window !== "undefined" ? window : this).deps(Graph.Heap); Graph.Geometry = (function(global){ function factoy(global, Graph){ var Intersection = { /* * Euclidean distance * Returns false or true */ distance: function(p0, p1){ var x1 = p0.x, y1 = p0.y; var x2 = p1.x, y2 = p1.y; return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); }, line: function(p0, p1){ return this.distance(p0, p1) <= p1.width; }, circle: function(p0, p1){ var dx = p1.x - p0.x, dy = p1.y - p0.y, dr = p0.radius + p1.radius; return dr * dr - dx * dx - dy * dy < 0.001; }, /* * Pie collision detection * Returns false or true * @param shapes is Shape{Object}, Contains the x, y, size, startAngle and endAngle * @param x and y is mouse event * @param checkin and checkout is callback */ pie: function(p0, p1){ var PI2 = Math.PI * 2; var dx = p0.x - p1.x, dy = p0.y - p1.y; var inPie = this.distance(p0, p1) <= p1.radius; if(inPie && typeof p1.innerRadius === "number") inPie = this.distance(p0, p1) >= p1.innerRadius; if(inPie){ var angle = Math.atan2(dy, dx) + Math.PI / 2;//顺、逆时针开始 if(angle < 0) angle += PI2; if(angle > PI2) angle -= PI2; if(angle >= p1.startAngle && angle <= p1.endAngle){ return inPie; } } return false; }, /* * Rect collision detection * Returns false or true * @param p0 is Point{Object}, Contains the x, y, size, startAngle and endAngle * @param p1 is Point{Object}, Contains the x, y, width, height. p1x = x + width, p1y = y + height */ rect: function(p0, p1){ var rx = (p0.x - p1.x) * (p0.x - p1.width); var ry = (p0.y - p1.y) * (p0.y - p1.height); return rx <= 0.0 && ry <= 0.0; }, polygon: function(p0, points){ var n = 0; for(var i = 0, length = points.length, j = length - 1; i < length; j = i, i++){ var source = points[i], target = points[j]; //点与多边形顶点重合或在多边形的边上 if( (source.x - p0.x) * (p0.x - target.x) >= 0 && (source.y - p0.y) * (p0.y - target.y) >= 0 && (p0.x - source.x) * (target.y - source.y) === (p0.y - source.y) * (target.x - source.x) ){ return true; } //点与相邻顶点连线的夹角 var angle = Math.atan2(source.y - p0.y, source.x - p0.x) - Math.atan2(target.y - p0.y, target.x - p0.x); //确保夹角不超出取值范围(-π 到 π) if(angle >= Math.PI) angle -= Math.PI * 2; else if(angle <= -Math.PI) angle += Math.PI * 2; n += angle; } return Math.round(n / Math.PI) !== 0;//当回转数为 0 时,点在闭合曲线外部。 } }; /** * Dash Line */ var arcDashLine = function(context, cx, cy, radius, startAngle, endAngle, dasharrays){ var length = pack("number", dasharrays && dasharrays[0], 1), offset = pack("number", dasharrays && dasharrays[1], 2); var size = PI / radius / (2 / length); var sa = startAngle, ea = sa + size; offset = PI / radius / (2 / offset); while(ea < endAngle){ context.beginPath(); context.arc(cx, cy, radius, sa, ea, false); context.stroke(); sa = ea + offset; ea = sa + size; } }; var DashLine = { line: function(x, y, w, h, dasharray){ var dx = w - x, dy = h - y, length = Math.sqrt(dx * dx + dy * dy), angle = Math.atan2(dy, dx); var dal = dasharray.length, di = 0; var isDraw = true; return function(context){ context.save(); context.translate(x, y); context.rotate(angle); context.moveTo(x = 0, 0); while(length > x){ x += dasharray[di++ % dal]; if(x > length) x = length; context[isDraw ? "lineTo" : "moveTo"](x, 0); isDraw = !isDraw; } context.stroke(); context.restore(); }; }, arc: function(context, cx, cy, radius, startAngle, endAngle, type){ arcDashLine(context, cx, cy, radius, startAngle, endAngle, { dot: [2, 6], dash: [8, 6], shortdash: [6, 2], shortdot: [2, 2], longdash: [16, 6] }[type] || [2, 6]); }, solid: function(context, x, y, w, h){ //this.line(x, y, w, h, [w])(context); context.moveTo(x, y); context.lineTo(w, h); context.stroke(); }, dot: function(context, x, y, w, h){ this.line(x, y, w, h, [2, 6])(context); }, dash: function(context, x, y, w, h){ this.line(x, y, w, h, [8, 6])(context); }, shortdash: function(context, x, y, w, h){ this.line(x, y, w, h, [6, 2])(context); }, shortdot: function(context, x, y, w, h){ this.line(x, y, w, h, [2, 2])(context); }, shortdashdot: function(context, x, y, w, h){ this.line(x, y, w, h, [6, 2, 2, 2])(context); }, shortdashdotdot: function(context, x, y, w, h){ this.line(x, y, w, h, [6, 2, 2, 2, 2, 2])(context); }, longdash: function(context, x, y, w, h){ this.line(x, y, w, h, [16, 6])(context); }, dashdot: function(context, x, y, w, h){ this.line(x, y, w, h, [8, 6, 2, 6])(context); }, longdashdot: function(context, x, y, w, h){ this.line(x, y, w, h, [16, 6, 2, 6])(context); }, longdashdotdot: function(context, x, y, w, h){ this.line(x, y, w, h, [16, 6, 2, 6, 2, 6])(context); } }; /** * Line **/ var Line = { smooth: function(prevPoint, curPoint, nextPoint, inverted){ var smoothing = 1.5, denom = smoothing + 1; var leftContX, leftContY, rightContX, rightContY; var x, y, prevX, prevY, nextX, nextY; var correction, ret = null; x = curPoint.x; y = curPoint.y; if(prevPoint && nextPoint){ prevX = prevPoint.x; prevY = prevPoint.y; nextX = nextPoint.x; nextY = nextPoint.y; leftContX = (prevX + smoothing * x) / denom; leftContY = (prevY + smoothing * y) / denom; rightContX = (nextX + smoothing * x) / denom; rightContY = (nextY + smoothing * y) / denom; if(inverted){ correction = ((rightContX - leftContX) * (rightContY - y)) / (rightContY - leftContY) + x - rightContX; leftContX += correction; rightContX += correction; if(leftContX > prevX && leftContX > x){ leftContX = Math.max(prevX, x); rightContX = x * 2 - leftContX; } else if(leftContX < prevX && leftContX < x){ leftContX = Math.min(prevX, x); rightContX = x * 2 - leftContX; } if(rightContX > nextX && rightContX > x){ rightContX = Math.max(nextX, x); leftContX = x * 2 - rightContX; } else if(rightContX < nextX && rightContX < x){ rightContX = Math.min(nextX, x); leftContX = x * 2 - rightContX; } } else{ correction = ((rightContY - leftContY) * (rightContX - x)) / (rightContX - leftContX) + y - rightContY; leftContY += correction; rightContY += correction; if(leftContY > prevY && leftContY > y){ leftContY = Math.max(prevY, y); rightContY = y * 2 - leftContY; } else if(leftContY < prevY && leftContY < y){ leftContY = Math.min(prevY, y); rightContY = y * 2 - leftContY; } if(rightContY > nextY && rightContY > y){ rightContY = Math.max(nextY, y); leftContY = y * 2 - rightContY; } else if(rightContY < nextY && rightContY < y){ rightContY = Math.min(nextY, y); leftContY = y * 2 - rightContY; } } curPoint.rightContX = rightContX; curPoint.rightContY = rightContY; } if(prevPoint){ ret = { x1: prevPoint.rightContX || prevPoint.x, y1: prevPoint.rightContY || prevPoint.y, x2: leftContX || x, y2: leftContY || y, x: x, y: y }; } return ret; }, Dash: DashLine }; /** * Rect **/ var Rect = { getRotationBound: function(width, height, angle){ var sin = Math.sin, cos = Math.cos, abs = Math.abs; if(angle === 0){ return { width: width, height: height}; } var x0 = abs(sin(angle = angle * Math.PI / 180) * width), x1 = abs(cos(angle) * width); var y0 = abs(sin(angle) * height), y1 = abs(cos(angle) * height); return { width: x1 + y0, height: x0 + y1 }; } }; var Geometry = { Intersection: Intersection, Line: Line, Rect: Rect }; return Geometry; } var exports = { deps: function(){ var args = Array.prototype.slice.call(arguments, 0); return factoy.apply(global, [global].concat(args)); } }; if(typeof module === "object" && module.exports){ module.exports = exports; } else if(typeof define === "function" && define.amd){ define(function(){ return exports; }); } return exports; })(typeof window !== "undefined" ? window : this).deps(Graph); Graph.Color = (function(){ var toString = Object.prototype.toString; var defined = function(a) { return typeof a !== "undefined" && a !== null; }; var isObject = function(a){ return toString.call(a) === "[object Object]"; }; var isArray = function(a){ return toString.call(a) === "[object Array]"; }; var isNumber = function(a){ return toString.call(a) === "[object Number]"; }; var extend = function(a, b){ var n; if(!isObject(a) && !(toString.call(a) === "[object Function]")){ a = {}; } for(n in b){ var src = a[n], copy = b[n]; if(src === copy) continue; if(copy && isObject(copy)){ a[n] = extend(src, copy); } else if(copy !== undefined){ a[n] = copy; } } return a; }; var clamp = function(v, min, max){ return v < min ? min : v > max ? max : v; }; var rHEX = /^#(([\da-f])([\da-f])([\da-f])([\da-f]{3})?)$/i, rRGBA = /(rgba?)\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d\.]+))?\)/, rHSL = /(hsl?)\((\d+),\s*(\d+)%,\s*(\d+)%\)/; var parse = function(color){ var rgba = rHEX.exec(color), value, table; if((table = Color2.LOOKUP_TABLE).hasOwnProperty(color)){ value = defined(table.a) ? table : extend({a: 1}, table[color]); } if(rgba){ value = rgba[5] ? rgba[1] : [rgba[2], rgba[2], rgba[3], rgba[3], rgba[4], rgba[4]].join("");//#000 to #0000000 value = parseInt(value, 16); value = { r: (value >>> 16) & 0xff, g: (value >>> 8) & 0xff, b: value & 0xff, a: 1 }; } rgba = rRGBA.exec(color); if(rgba){ value = { r: rgba[2] | 0, g: rgba[3] | 0, b: rgba[4] | 0, a: rgba[1] === "rgb" ? 1 : (parseFloat(rgba[5], 10)) }; isNumber(value.a) || (value.a = 1); } rgba = rHSL.exec(color); if(rgba){ var hue2rgb = function(p, q, t){ //linear interpolate a + (b - a) * t t < 0 && t++; t > 1 && t--; return t < 1 / 6 ? p + (q - p) * t * 6 : t < 1 / 2 ? q : t < 2 / 3 ? p + (q - p) * (2 / 3 - t) * 6 : p;//[0/360,45,90,135,180,225,270,315] lerp }; var h = parseFloat(rgba[2], 10) / 360 % 360, s = Math.min(1, Math.max(0, parseFloat(rgba[3], 10) / 100)), l = Math.min(1, Math.max(0, parseFloat(rgba[4], 10) / 100)); h < 0 && h++; if(s === 0){ s = h = l;//achromatic 消色 } else{ var q = l < 0.5 ? l * (s + 1) : l + s - l * s, p = l * 2 - q; s = hue2rgb(p, q, h + 1 / 3);//360 / 3 l = hue2rgb(p, q, h); h = hue2rgb(p, q, h - 1 / 3); } value = { r: ~~(s * 256), g: ~~(l * 256), b: ~~(h * 256), a: 1 }; } if(!value){ value = {r: 0, g: 0, b: 0, a: 1}; } return value; }; var Color2 = {}; Color2.RED_MASK = 0x00ff0000; Color2.GREEN_MASK = 0x0000ff00; Color2.BLUE_MASK = 0x000000ff; Color2.ALPHA_MASK = 0xff000000; Color2.LOOKUP_TABLE = { aqua: {r: 0, g: 255, b: 255, a: 1}, lime: {r: 0, g: 255, b: 0, a: 1}, silver: {r: 192, g: 192, b: 192, a: 1}, black: {r: 0, g: 0, b: 0, a: 1}, maroon: {r: 128, g: 0, b: 0, a: 1}, teal: {r: 0, g: 128, b: 128, a: 1}, blue: {r: 0, g: 0, b: 255, a: 1}, navy: {r: 0, g: 0, b: 128, a: 1}, white: {r: 255, g: 255, b: 255, a: 1}, fuchsia: {r: 255, g: 0, b: 255, a: 1}, olive: {r: 128, g: 128, b: 0, a: 1}, yellow: {r: 255, g: 255, b: 0, a: 1}, orange: {r: 255, g: 165, b: 0, a: 1}, gray: {r: 128, g: 128, b: 128, a: 1}, purple: {r: 128, g: 0, b: 128, a: 1}, green: {r: 0, g: 128, b: 0, a: 1}, red: {r: 255, g: 0, b: 0, a: 1}, pink: {r: 255, g: 192, b: 203, a: 1}, cyan: {r: 0, g: 255, b: 255, a: 1}, transparent: {r: 255, g: 255, b: 255, a: 0} }; Color2.red = function(value){ return ((value & Color2.RED_MASK) >>> 16); }; Color2.green = function(value){ return ((value & Color2.GREEN_MASK) >>> 8); }; Color2.blue = function(value){ return (value & Color2.BLUE_MASK); }; Color2.alpha = function(value){ return ((value & Color2.ALPHA_MASK) >>> 24); }; extend(Color2, { isColor: function(color){ return Color2.LOOKUP_TABLE.hasOwnProperty(color) || rHEX.exec(color) || rRGBA.exec(color); }, rgb: function(rgb){ return "rgba(" + rgb.r + "," + rgb.g + "," + rgb.b + ")"; }, rgba: function(rgba){ return "rgba(" + rgba.r + "," + rgba.g + "," + rgba.b + "," + rgba.a + ")"; }, hex: function(rgba){ var f; return "#" + (f = function(c){ return (c = Math.max(0, Math.min(c, 0xff)).toString(16), c.length < 2 ? "0" + c : c); }, f(rgba.r)) + f(rgba.g) + f(rgba.b); }, toString: function(c){ return "rgba(" + ((c & Color2.RED_MASK) >>> 16) + "," + ((c & Color2.GREEN_MASK) >>> 8) + "," + ((c & Color2.BLUE_MASK)) + "," + ((c & Color2.ALPHA_MASK) >>> 24) / 255 + ")"; }, interpolate: function(a, b){ var ar, ag, ab, br, bg, bb; a = parse(a), b = parse(b); br = b.r - (ar = a.r), bg = b.g - (ag = a.g), bb = b.b - (ab = a.b); return function(t){ return Color2.hex({ r: Math.round(ar + br * t),//at + b g: Math.round(ag + bg * t), b: Math.round(ab + bb * t) }); }; }, lerp: (function(){ var uninterpolateNumber = function(a, b){ b = b - (a = +a) ? 1 / (b - a) : 0; return function(x){ return (x - a) * b; }; }; //uninterpolateNumber return function(domain, range, interpolateRGB){ var numberFns = [], colorFns = []; var length = Math.min(domain.length, range.length) - 1, i = 1; if(domain[length] < domain[0]){ domain = domain.slice().reverse(); range = range.slice().reverse(); } for(; i <= length; i++){ numberFns.push(uninterpolateNumber(domain[i - 1], domain[i]));//prev & current colorFns.push(interpolateRGB(range[i - 1], range[i])); } return function(x){ var l = 1, r = length, m; while(l < r){ m = l + r >> 1; x < domain[m] ? (r = m) : (l = m + 1); } return colorFns[l -= 1](numberFns[l](x)); }; }; })() }); /** * create static Color parse */ var Color = extend({}, Color2); Color.parse = function(color){ return new Color.prototype.init(color);// {r: 0, g: 0, b: 0, a: 1};//default black }; Color.prototype = { init: function(color){ var rgba; this.a = +!(this.r = this.g = this.b = 0); if(Color2.isColor(color)){ rgba = parse(color); this.r = rgba.r; this.g = rgba.g; this.b = rgba.b; this.a = rgba.a; } else if(isObject(color) && (color.hasOwnProperty("radialGradient") || color.hasOwnProperty("linearGradient"))){ Color.Gradient.parse.call(this, color); } return this; }, add: function(c1, c2){ //return c1 + c2 & 0xff; //return Math.min(0xff, c1 + c2); //return (c2 < 128) ? (2 * c1 * c2 / 255) : (255 - 2 * (255 - c1) * (255 - c2) / 255); return (c1 < 128) ? (2 * c1 * c2 / 255) : (255 - 2 * (255 - c1) * (255 - c2) / 255); }, linear: function(x1, y1, x2, y2){ var context = Color.GRADIENT_CONTEXT; if(defined(context)){ var gradient = context.createLinearGradient( this.x1 * x1, this.y1 * y1, this.x2 * x2, this.y2 * y2 ); this.stops.forEach(function(item){ gradient.addColorStop(item[0], item[1]); }); return gradient; } return null; }, radial: function(cx, cy, cr){ var context = Color.GRADIENT_CONTEXT; if(defined(context)){ cx = isNumber(cx) ? cx : 0; cy = isNumber(cy) ? cy : 0; cr = isNumber(cr) ? cr : 0; var xoff = this.cx0 * cr, yoff = this.cy0 * cr; var gcx = xoff + cx, gcy = yoff + cy, or = this.cr0 * cr + Math.sqrt(xoff * xoff + yoff * yoff); //console.log(cr, cx, cy, or, gcx); var gradient = context.createRadialGradient( gcx, gcy, 0, gcx, gcy, or ); this.stops.forEach(function(item){ gradient.addColorStop(item[0], item[1]); }); return gradient; } return null; } }; Color.prototype.init.prototype = Color.prototype; extend(Color.prototype, Color2); extend(Color.prototype, { rgba: function(){ return Color2.rgba(this); }, rgb: function(){ return Color2.rgb(this); }, alpha: function(a){ if(!arguments.length){ return this.a; } this.a = Math.max(0, Math.min(1, a)); return this; }, hex: function(){ return Color2.hex(this); }, hsl: function(){ }, interpolate: function(b){ return Color2.interpolate(this, b); }, value: function(){ return this.a << 24 | this.r << 16 | this.g << 8 | this.b; } }); //Color.RadialGradient.parse(fillColor.radialGradient).context(context); Color.GRADIENT_CONTEXT = null; Color.Gradient = { parse: function(color){ var radialGradient, cx0, cy0, cr0; var linearGradient, x1, y1, x2, y2; var stops = []; if(defined(radialGradient = color.radialGradient)){ this.cx0 = cx0 = clamp(isNumber(cx0 = (radialGradient = radialGradient || {}).cx) ? cx0 : 1, 0, 1); this.cy0 = cy0 = clamp(isNumber(cy0 = radialGradient.cy) ? cy0 : 1, 0, 1); this.cr0 = cr0 = clamp(isNumber(cr0 = radialGradient.r) ? cr0 : 0, 0, 1); } if(defined(linearGradient = color.linearGradient)){ this.x1 = x1 = clamp(isNumber(x1 = (linearGradient = linearGradient || {}).x1) ? x1 : 0, 0, 1); this.y1 = y1 = clamp(isNumber(y1 = linearGradient.y1) ? y1 : 0, 0, 1); this.x2 = x2 = clamp(isNumber(x2 = linearGradient.x2) ? x2 : 0, 0, 1); this.y2 = y2 = clamp(isNumber(y2 = linearGradient.y2) ? y2 : 0, 0, 1); } if(isArray(color.stops)){ color.stops.forEach(function(item){ var r = isNumber(item[0]) ? item[0] : 1, c = Color2.isColor(item[1]) ? item[1] : "#000"; stops.push([clamp(r, 0, 1), c]); }); } this.stops = stops; this.color = stops.length ? (stops[stops.length - 1][1]) : "#000"; } }; if(typeof module === "object" && module.exports){ module.exports = Color; } else if(typeof define === "function" && define.amd){ define([], function(){ return Color; }); } return Color; })(); Graph.Text = (function(global){ var document = global.document; var sin = Math.sin, cos = Math.cos, abs = Math.abs; var _toString = Object.prototype.toString; var isArray = function(v){ return _toString.call(v) === "[object Array]"; }; /** * Text */ var Text = { _cache: { width: {}, height: {} }, context: function(context){ Text._context = context; }, getWidth: function(text, style){ var fontFamily = (style = style || {}).fontFamily || "sans-serif", fontSize = style.fontSize || "12px", fontWeight = style.fontWeight || "normal", fontStyle = style.fontStyle || "normal", lineHeight = style.lineHeight || "normal"; var font = [ fontStyle, fontWeight, fontSize + "/" + lineHeight, fontFamily ].join(" "); text = "" + (text || "Eg"); var width = 0; var id = text + "-" + font; if(Text._cache.width[id]){ return Text._cache.width[id]; } var context = Text._context, canvas; if(document && document.createElement){ context = (canvas = document.createElement("canvas")).getContext("2d"); } else if(context){ canvas = context.canvas; } if(!context){ return 0; } Text._context = context; //console.log(font, text) text.split("\n").forEach(function(){ context.font = font; width = Math.max(width, context.measureText(text).width); }); return (Text._cache.width[id] = width); }, getHeight: function(text, style){ var fontFamily = (style = style || {}).fontFamily || "sans-serif", fontSize = style.fontSize || "12px", fontWeight = style.fontWeight || "normal", fontStyle = style.fontStyle || "normal", lineHeight = style.lineHeight || "normal"; var font = [ fontStyle, fontWeight, fontSize + "/" + lineHeight, fontFamily ].join(" "); if(String(text).length === 0){ return 0; } text = "" + (text || "Eg"); var id = text + "-" + font; if(Text._cache.height[id]){ return Text._cache.height[id]; } var context = Text._context, canvas; if(context){ canvas = context.canvas; if(typeof (Text._cache.height[id] = context.measureText(text).emHeightAscent) === "number"){ return Text._cache.height[id]; } } else{ Text._context = context = (canvas = document.createElement("canvas")).getContext("2d"); } var width = Math.ceil(Text.getWidth(text, style)), height = parseFloat(fontSize, 10) || 12, top, bottom; var data; context.save(); context.font = font; context.textBaseline = "alphabetic"; context.textAlign = "left"; context.fillStyle = "#fff"; context.fillText(text, 0, Math.ceil(height)); data = context.getImageData(0, 0, width, height).data; context.restore(); top = bottom = -1; for(var y = 0; y <= height; y++){ for(var x = 0; x < width; x++){ var i = x + y * width << 2; if(data[i] + data[i + 1] + data[i + 2] > 0){ if(top === -1) top = y;//once bottom = y; break; } } } //console.log(bottom - top + 1, text, font) return Text._cache.height[id] = bottom - top + 1; }, measureText: function(text, style){ var angle = style && style.rotation, width = 0, height = 0; var bbox = { left: 0, top: 0, width: width, height: height }; if(!(text = String(text)).length) return bbox; bbox.width = width = Text.getWidth(text, style); bbox.height = height = Text.getHeight(text, style); if(style && typeof angle === "number" && isFinite(angle) && !isNaN(angle)){ var x0 = abs(sin(angle = angle * Math.PI / 180) * width), x1 = abs(cos(angle) * width); var y0 = abs(sin(angle) * height), y1 = abs(cos(angle) * height); bbox.width = x1 + y0; bbox.height = x0 + y1; } return bbox; }, multipText: function(word, maxWidth, options){ var ellipsis = (options = options || {}).ellipse || ".."; var context = Text._context; if(!context){ Text._context = context = (document.createElement("canvas")).getContext("2d"); } var textWidth = (Text.getWidth(word, options));//context.measureText(word).width, //curWidth = 0; if(textWidth <= maxWidth) return word; maxWidth -= Text.getWidth(ellipsis); var l = 0, r = word.length, m; while(l < r){ m = (l + r >>> 1) + 1; if(Text.getWidth(word.slice(0, m), options) < maxWidth) l = m; else r = m - 1; } return word.slice(0, l) + ellipsis; } }; Text.HTML = function(nodes, g, options){ var fontSize = (options = options || {}).fontSize || "12px", fontFamily = options.fontFamily || "Arial, sans-serif", fontWeight = options.fontWeight || "100",//normal | bold | bolder | lighter | auto | inherit | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 fontStyle = options.fontStyle || "normal";//normal | italic | oblique | inherit //color = options.color || (g && g.fillStyle); var bbox = {height: 0, width: 0}, width = 0, height = 0, x = 0, y = 0; var lastHeight = 0; function parse(nodes, isTag, render){ var i, length = nodes.length | (i = 0); var curFontWeight = fontWeight, curFontStyle = fontStyle, node; for(; i < length; i++){ node = nodes[i]; if(node.type === "br"){ x = 0; y += bbox.height; continue; } if(node.type === "b"){ g.font = [curFontStyle, curFontWeight = "bold", fontSize, fontFamily].join(" "); } else if(node.type === "i"){ g.font = [curFontStyle = "italic", curFontWeight, fontSize, fontFamily].join(" "); } else if(node.type === "normal" && !isTag){ curFontWeight = fontWeight; g.font = [curFontStyle, curFontWeight, fontSize, fontFamily].join(" "); } if(node.type === "normal"){ render && render.call(g, node.value, x, height); bbox = Text.measureText(node.value, options); width = Math.max(width, bbox.width); height = y + bbox.height; x += bbox.width; lastHeight = Math.max(lastHeight, bbox.height); } if(node.type === "i" || node.type === "u" || node.type === "b" ){ (isArray(node.value)) && parse(node.value, true, render); curFontStyle = fontStyle; curFontWeight = fontWeight; } } } var tag = { getBBox: function(){ x = y = width = height = 0;