jj-chart
Version:
copy mychart
1,556 lines (1,442 loc) • 683 kB
JavaScript
/**
* @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;