一个html5 canvas 绘图框架

(function(win) {
    if (win.canvasUtils) {
        return;
    } else {
        var cu = win.canvasUtils = {};
    }

    if (!Array.prototype.indexOf){
        Array.prototype.indexOf = function(elt){
            var len = this.length >>> 0;
            var from = Number(arguments[1]) || 0;
            from = (from < 0) ? Math.ceil(from) : Math.floor(from);
            if (from < 0)from += len;
            for (; from < len; from++){
                if (from in this && this[from] === elt)return from;
            }
            return -1;
        };
    }
    if (!Array.prototype.remove){
        Array.prototype.remove = function(elt){
            for (var i=-1, len=this.length; ++i<len;) {
                if (this[i] === elt) {
                    this.splice(i, 1);
                    break;
                }
            }
        };
    }

    cu.createClass = function (Parent) {
        var klass = function () {
            this.init.apply(this, arguments);
        };
        if (Parent) {
            function F() {}
            F.prototype = Parent.prototype;
            var prototype = new F();
            prototype.constructor = klass;
            klass.prototype = prototype;
            klass._super = Parent;
        }
        klass.fn = klass.prototype;
        klass.fn.init = function () {};
        klass.fn.proxy = function (fn) {
            var thes = this;
            return function () {
                return fn.apply(thes, arguments);
            };
        };
        klass.extend = function (obj) {
            for (var i in obj) {
                klass[i] = obj[i];
            }
            var extended = obj.extended;
            if (extended)    extended(klass);
        };
        klass.include = function (obj) {
            for (var i in obj) {
                klass.fn[i] = obj[i];
            }
            var included = obj.included;
            if (included)    included(klass);
        };
        return klass;
    };

    cu.guid = function() {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,function (c) {
            var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        }).toUpperCase();
    };

    win.requestNextAnimationFrame = (function () {
        var originalWebkitMethod,
            wrapper = undefined,
            callback = undefined,
            geckoVersion = 0,
            userAgent = navigator.userAgent,
            index = 0,
            self = this;
        if (window.webkitRequestAnimationFrame) {
            wrapper = function (time) {
                if (time === undefined) {
                    time = +new Date();
                }
                self.callback(time);
            };
            originalWebkitMethod = window.webkitRequestAnimationFrame;
            window.webkitRequestAnimationFrame =
                function (callback, element) {
                    self.callback = callback;
                    originalWebkitMethod(wrapper, element);
                }
        }
        if (window.mozRequestAnimationFrame) {
            index = userAgent.indexOf('rv:');
            if (userAgent.indexOf('Gecko') != -1) {
                geckoVersion = userAgent.substr(index + 3, 3);
                if (geckoVersion === '2.0') {
                    window.mozRequestAnimationFrame = undefined;
                }
            }
        }
        return window.requestAnimationFrame ||
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame ||
            window.oRequestAnimationFrame ||
            window.msRequestAnimationFrame ||
            function (callback, element) {
                var start,
                    finish;
                window.setTimeout(function () {
                    start = +new Date();
                    callback(start);
                    finish = +new Date();
                    self.timeout = 1000 / 60 - (finish - start);
                }, self.timeout);
            };
    })();

    var _images = {}
    cu.getImage = function(name) {
        return _images[name];
    };

    var logger = {
        debug: function(msg) {
            if (this.debugLevel >= logger.LEVEL_DEBUG) {
                console.log(msg);
            }
        },
        info: function(msg) {
            if (this.debugLevel >= logger.LEVEL_INFO) {
                console.log(msg);
            }
        },
        warn: function(msg) {
            if (this.debugLevel >= logger.LEVEL_WARN) {
                console.log(msg);
            }
        },
        error: function(msg) {
            if (this.debugLevel >= logger.LEVEL_ERROR) {
                console.log(msg);
            }
        },
        LEVEL_DEBUG: 4,
        LEVEL_INFO: 3,
        LEVEL_WARN: 2,
        LEVEL_ERROR: 1
    };
    logger.debugLevel = logger.LEVEL_INFO;

    cu.loadImages = function(options, fn) {
        var total = options.length;
        var alreadyHave = true;
        for (var i=-1, len=total; ++i<len;) {
            var opt = options[i];
            if (_images[opt.name]) {
                total--;
                continue;
            } else {
                alreadyHave = false;
                var image = new Image();
                image.onload = (function(name, image, opt) {
                    return function() {
                        var a = _images[name] = {};
                        a.image = image;
                        a.width = image.width;
                        a.height = image.height;
                        image.onload = null;
                        total--;
                        if (total === 0 && fn) {
                            logger.info('all asyn image loaded');
                            fn();
                        }
                    }
                })(opt.name, image, opt)
                image.src = opt.url;
            }
        }
        if (alreadyHave && fn) {
            fn();
        }
    };

    cu.g = document.createElement('canvas').getContext('2d');
    //cu.g.font = '12px sans-serif';

    cu.Event = cu.createClass();
    cu.Event.extend({
        CLICK: 'click',
        DBCLICK: 'dblclick',
        MOUSEDOWN: 'mousedown',
        MOUSEUP: 'mouseup',
        MOUSEMOVE: 'mousemove',
        MOUSEOVER: 'mouseover',
        MOUSEOUT: 'mouseout',
        MOUSEWHEEL: 'mousewheel',
        FOCUS: 'focus',
        BLUR: 'blur',
        INVALIDATE: 'invalidate',
        SCROLL: 'scroll',
        addEventListener: function (n, t, f, b) {
            if (b == null)b = false;
            if (n.addEventListener) {// IE9, Chrome
                n.addEventListener(t, f, b);
                if (t === 'mousewheel') {// Firefox
                    n.addEventListener('DOMMouseScroll', f, b);
                }
            } else if (n.attachEvent) {// IE 6/7/8
                n["e" + t + f] = f;
                n[t + f] = function () {
                    n["e" + t + f]();
                };
                n.attachEvent("on" + t, n[t + f]);
            }
        },
        preventDefault: function(e) {
            if (e.preventDefault) {
                e.preventDefault()
            }
            if (e.preventManipulation) {
                e.preventManipulation()
            } else {
                e.returnValue = false
            }
            return false;
        }
    });

    cu.Event.include({
        init: function(type, target, e, p1, p2) {
            this.type = type;
            this.target = target;
            this.e = e ? e : null;// 浏览器事件
            this.p1 = p1 ? p1 : null;// viewRect内坐标
            this.p2 = p2 ? p2 : null;// elmsRect内坐标
        }
    });

    cu.Style = {
        BACKGROUND_COLOR: 'background.color',
        PADDING: 'padding',
        MARGIN: 'margin',
        OUTLINE_WIDTH: 'outline.width',
        OUTLINE_COLOR: 'outline.color',
        INLINE_WIDTH: 'inline.width',
        INLINE_COLOR: 'inline.color',
        SHAPE: 'shape',
        LABEL_DIRECTION: 'label.direction',
        LABEL_GAP: 'label.gap',
        LABEL_PADDING: 'label.padding',
        LABEL_FONT_COLOR: 'label.font.color',
        LABEL_BACKGROUND_COLOR: 'label.background.color',
        LABEL_CORNER_RADIOUS: 'label.corner.radious',
        LABEL_MAXTEXTWIDTH: 'label.maxtextwidth',

        FONT: 'font',
        FONT_COLOR: 'font.color',
        CORNER_RADIOUS: 'corner.radius',

        COLOR: 'color',
        WIDTH: 'scrollbar.width',
        LINE_WIDTH: 'line.width',
        ALPHA: 'alpha'
    };

    cu.EventListener = cu.createClass();
    cu.EventListener.include({
        init: function() {
            this.handlers = {};
        },
        on: function(type, fn) {
            if (!this.handlers[type]) {
                this.handlers[type] = [];
            }
            this.handlers[type].push(fn);
            return this;
        },
        off: function(type, fn) {
            var ls = this.handlers[type];
            if (ls) {
                for (var i=-1, len=ls.length; ++i<len;) {
                    if (ls[i] === fn) {
                        ls.splice(i, 1);
                    }
                }
            }
            return this;
        },
        handleOn: function(type, e, p1, p2) {
            var e = new cu.Event(type, this, e, p1, p2);
            var ls = this.handlers[type];
            if (ls) {
                for (var i=-1, len=ls.length; ++i<len;) {
                    ls[i].call(this, e);
                }
            }
        }
    });

    cu.Rectangle = cu.createClass();
    cu.Rectangle.extend({
        BASE_LEFTUP: 1,
        BASE_CENTER: 2,
        BASE_RIGTHBOTTOM: 3
    });
    cu.Rectangle.include({
        init: function(x, y, w, h) {
            this.x = x;
            this.y = y;
            this.w = w;
            this.h = h;
            this.cx = this.cy = this.r = this.b = null;
            this.update();
        },
        update: function(base) {
            base = base ? base : cu.Rectangle.BASE_LEFTUP;
            switch (base) {
                case cu.Rectangle.BASE_LEFTUP:
                    var hw = this.w / 2;
                    var hh = this.h / 2;
                    this.cx = this.x + hw;
                    this.cy = this.y + hh;
                    this.r = this.x + this.w;
                    this.b = this.y + this.h;
                    break;
                case cu.Rectangle.BASE_CENTER:
                    if (null != this.cx && null != this.cy) {
                        var hw = this.w / 2;
                        var hh = this.h / 2;
                        if (this.cx < hw) {
                            this.x = 0;
                            this.cx = hw;
                        } else {
                            this.x = this.cx - hw;
                        }
                        this.r = this.x + this.w;
                        if (this.cy < hh) {
                            this.y = 0;
                            this.cy = hh;
                        } else {
                            this.y = this.cy - hh;
                        }
                        this.b = this.y + this.h;
                    }
                    break;
                case cu.Rectangle.BASE_RIGTHBOTTOM:
                    if (null != this.r && null != this.b) {
                        if (this.r < this.w) {
                            this.x = 0;
                            this.r = this.w;
                        } else {
                            this.x = this.r - this.w;
                        }
                        this.cx = this.x + this.w / 2;

                        if (this.b < this.h) {
                            this.y = 0;
                            this.b = this.h;
                        } else {
                            this.y = this.b - this.h;
                        }
                        this.cy = this.y + this.h / 2;
                    }
                    break;
            }
        },
        deltaLocation: function(dx, dy) {
            this.x += dx;
            this.y += dy;
            this.update();
        },
        equals: function(that) {
            if (that) {
                return this.x === that.x && this.y === that.y && this.w === that.w && this.h === that.h;
            } else {
                return false;
            }
        },
        isContain: function(rect) {// 是否包含rect
            if (rect) {
                return this.x <= rect.x && this.y <= rect.y && this.r >= rect.r && this.b >= rect.b;
            } else {
                return false;
            }
        },
        isIn: function(x, y) {// 是否包含点x,y
            var l = this.x;
            var r = this.r;
            var t = this.y;
            var b = this.b;
            return x > l && x < r && y > t && y < b;
        },
        intersects: function(rect) {
            var i = this.w;
            var n = this.h;
            var r = rect.w;
            var a = rect.h;
            if (r <= 0 || a <= 0 || i <= 0 || n <= 0) {
                return false
            }
            var o = this.x;
            var s = this.y;
            var A = rect.x;
            var l = rect.y;
            r += A;
            a += l;
            i += o;
            n += s;
            return (r < A || r > o) && (a < l || a > s) && (i < o || i > A) && (n < s || n > l);
        },
        intersection: function(rect) {
            var i = this.x;
            var n = this.y;
            var r = rect.x;
            var a = rect.y;
            var o = i;
            o += this.w;
            var s = n;
            s += this.h;
            var A = r;
            A += rect.w;
            var l = a;
            l += rect.h;
            if (i < r)i = r;
            if (n < a)n = a;
            if (o > A)o = A;
            if (s > l)s = l;
            o -= i;
            s -= n;
            if (o === 0 || s === 0) {
                return null
            }
            return new cu.Rectangle(i, n, o, s);
        },
        copy: function(rect) {
            this.x = rect.x;
            this.y = rect.y;
            this.w = rect.w;
            this.h = rect.h;
            this.update();
        }
    });

    cu.DirtyRect = cu.createClass(cu.Rectangle);
    cu.DirtyRect.include({
        init: function() {
            this.isDirty = null;
            this.reset();
        },
        add: function(rect) {
            if (rect.w !== 0 && rect.h !== 0) {
                this.x = rect.x < this.x ? rect.x : this.x;
                this.r = rect.r > this.r ? rect.r : this.r;
                this.y = rect.y < this.y ? rect.y : this.y;
                this.b = rect.b > this.b ? rect.b : this.b;
                this.w = this.r - this.x;
                this.h = this.b - this.y;
                this.update();
                this.isDirty = true;
            }
        },
        reset: function() {
            var m = Number.MAX_VALUE;
            cu.DirtyRect._super.fn.init.call(this, m, m, -m ,-m);
            this.isDirty = false;
        }
    })

    cu.QuadTree = cu.createClass();
    cu.QuadTree.include({
        init: function(rect) {
            var self = this;
            self.q1 = null;
            self.q2 = null;
            self.q3 = null;
            self.q4 = null;
            self.parent = null;
            self.data = [];
            self.rect = rect;
            self.root = self;

        },
        createChildren:function (deep){
            if (deep == 0)return;
            var self = this;
            var hw = self.rect.width / 2 , hh = self.rect.height / 2;
            self.q1 = new cu.QuadTree(new cu.Rectangle(self.rect.x + hw, self.rect.y, hw, hh));
            self.q2 = new cu.QuadTree(new cu.Rectangle(self.rect.x + hw, self.rect.y + hh, hw, hh));
            self.q3 = new cu.QuadTree(new cu.Rectangle(self.rect.x, self.rect.y + hh, hw, hh));
            self.q4 = new cu.QuadTree(new cu.Rectangle(self.rect.x, self.rect.y, hw, hh));
            self.q1.parent = self.q2.parent = self.q3.parent = self.q4.parent = self;
            self.q1.root = self.q2.root = self.q3.root = self.q4.root = self.root;
            self.q1.createChildren(deep - 1);
            self.q2.createChildren(deep - 1);
            self.q3.createChildren(deep - 1);
            self.q4.createChildren(deep - 1);
        },
        updateChildren: function(rect) {
            this.rect = rect;
            if (this.hasChildren()) {
                var hw = this.rect.w / 2 , hh = this.rect.h / 2;
                this.q1.updateChildren(new cu.Rectangle(this.rect.x + hw, this.rect.y, hw, hh));
                this.q2.updateChildren(new cu.Rectangle(this.rect.x + hw, this.rect.y + hh, hw, hh));
                this.q3.updateChildren(new cu.Rectangle(this.rect.x, this.rect.y + hh, hw, hh));
                this.q4.updateChildren(new cu.Rectangle(this.rect.x, this.rect.y, hw, hh));
            }
        },
        hasChildren:function(){
            return this.q1 || this.q2 || this.q3 || this.q4;
        },
        add: function(v) {
            if (!this.rect.isContain(v.rect)) {
                return null;
            }
            if (!v.rect.isIn(this.rect.cx, this.rect.cy) && this.hasChildren()) {
                var t = this.q1.add(v);
                if (t)  return t;
                t = this.q2.add(v);
                if (t)  return t;
                t = this.q3.add(v);
                if (t)  return t;
                t = this.q4.add(v);
                if (t)  return t;
                //return this.q1.add(v) || this.q2.add(v) || this.q3.add(v) || this.q4.add(v);
            }
            this.data.push(v);
            return this;
        },
        isContain: function(v) {
            return (this.rect.isContain(v.rect) && v.rect.isIn(this.rect.cx, this.rect.cy));
        },
        getDataInRect: function(rect){
            if (!this.rect.intersects(rect)) return [];
            var r = [];
            for (var i=-1, len=this.data.length; ++i<len;) {
                var v = this.data[i];
                if (v.rect.intersects(rect)) {
                    r.push(v);
                }
            }
            if (this.hasChildren()){
                r = r.concat(this.q1.getDataInRect(rect));
                r = r.concat(this.q2.getDataInRect(rect));
                r = r.concat(this.q3.getDataInRect(rect));
                r = r.concat(this.q4.getDataInRect(rect));
            }
            return r;
        },
        getDataOnPoint: function(x, y) {
            if (!this.rect.isIn(x, y)) return [];
            var r = [];
            for (var i=-1, len=this.data.length; ++i<len;) {
                var v = this.data[i];
                if (v.rect.isIn(x, y)) {
                    r.push(v);
                }
            }
            if (this.hasChildren()) {
                r = r.concat(this.q1.getDataOnPoint(x, y));
                r = r.concat(this.q2.getDataOnPoint(x, y));
                r = r.concat(this.q3.getDataOnPoint(x, y));
                r = r.concat(this.q4.getDataOnPoint(x, y));
            }
            return r;
        },
        getData: function() {
            var ret = this.data.concat();
            if (this.hasChildren()) {
                ret = ret.concat(this.q1.getData());
                ret = ret.concat(this.q2.getData());
                ret = ret.concat(this.q3.getData());
                ret = ret.concat(this.q4.getData());
            }
            return ret;
        }
    });

    cu.Layer = cu.createClass();
    cu.Layer.include({
        init: function(id) {
            this.id = id ? id : cu.guid();
            this.rect = new cu.Rectangle(0, 0, 0, 0);
            this.elms= [];
            this.elmId2Tree = {};
            this.changedElm = {};
            this.quadTree = new cu.QuadTree(this.rect);
            this.quadTree.createChildren(3);
            this.isVisible = true;
            this.elmIndex = 0;
        },
        add: function(v) {
            if (this.elms.indexOf(v) === -1) {
                this.increaseElmIndex(v);
                this.elms.push(v);
                this.elmId2Tree[v.id] = null;
                this.changedElm[v.id] = v;
            }
        },
        increaseElmIndex: function(v) {
            v.indexInLayer = ++this.elmIndex;
        },
        batchIncreaseElmIndex: function(arrV) {
            // TODO 框选多个元素时需要批量增加这些元素在同一layer里的索引号
        },
        remove: function(v) {
            this.elms.remove(v);
            if (this.elmId2Tree[v.id]) {
                this.elmId2Tree[v.id].data.remove(v);
                delete this.elmId2Tree[v.id];
            }
        },
        empty: function() {
            var v = null;
            this.elmIndex = 0;
            while ((v = this.elms.pop()) != undefined) {
                if (this.elmId2Tree[v.id]) {
                    this.elmId2Tree[v.id].data.remove(v);
                    delete this.elmId2Tree[v.id];
                }
            }
        },
        update: function(v) {
            if (this.elms.indexOf(v) !== -1) {
                this.changedElm[v.id] = v;
            }
        },
        invalidate: function(context, dirtyRect) {
            var elms = this.quadTree.getDataInRect(dirtyRect);
            elms.sort(function(a, b) {
                return a.indexInLayer - b.indexInLayer;
            });
/*            var tt = [];
            for (var i=-1, len=elms.length; ++i<len;) {
                tt.push(elms[i].indexInLayer);
            }
            console.log(tt.join(','));*/
            for (var i=-1, len=elms.length; ++i<len;) {
                elms[i].draw(context);
            }
        },
        updateQuadTree: function() {
            var r = 0;
            var b = 0;
            var v = null;// 成员
            var t = null;// 树节点
            for (var i=-1, len=this.elms.length; ++i<len;) {
                v = this.elms[i];
                r = v.rect.r > r ? v.rect.r : r;
                b = v.rect.b > b ? v.rect.b : b;
            }
            if (this.rect.r !== r || this.rect.b !== b) {// 重建索引矩形,重新索引所有成员
                this.rect.w = r;
                this.rect.h = b;
                this.rect.update();
                this.quadTree.updateChildren(this.rect);
                for (var i=-1, len=this.elms.length; ++i<len;) {
                    v = this.elms[i];
                    t = this.elmId2Tree[v.id];
                    if (t && !t.isContain(v.rect)) {
                        t.data.remove(v);
                        this.elmId2Tree[v.id] = this.quadTree.add(v);
                    } else if (!t) {
                        this.elmId2Tree[v.id] = this.quadTree.add(v);
                    }
                }
                this.changedElm = {};
            } else {// 重新索引新增或修改的成员
                var v = null;// 成员
                var t = null;// 树节点
                var io = 0;
                for (var h in this.changedElm) {
                    io++;
                }
                for (var id in this.changedElm) {
                    t = this.elmId2Tree[id];
                    v = this.changedElm[id];
                    if (t && !t.isContain(v.rect)) {
                        t.data.remove(v);
                        this.elmId2Tree[v.id] = this.quadTree.add(v);
                    } else if (!t) {
                        this.elmId2Tree[v.id] = this.quadTree.add(v);
                    }
                    delete this.changedElm[id];
                }
            }
        },
        findElementsOnPoint: function(x, y) {
            var elms = this.quadTree.getDataOnPoint(x, y);
/*            var tt = [];
            for (var i = -1, len = elms.length; ++i < len;) {
                tt.push(elms[i].indexInLayer);
            }
            console.log(tt.join(','));*/

            var len = elms.length;
            if (len > 0) {
                elms.sort(function(a, b) {
                    return a.indexInLayer - b.indexInLayer;
                });
            }
            return elms;
        }
    });

    cu.Panel = cu.createClass(cu.EventListener);
    cu.Panel.include({
        init: function(w, h) {
            cu.Panel._super.fn.init.call(this);
            w = w ? w : 330;
            h = h ? h : 150;
            this.scale = 1;// 大于1是放大,小于1是缩小
            this.dirtyRect = new cu.DirtyRect();
            this.isAllDirty = true;
            this.elmsRect = new cu.Rectangle(0, 0, 0, 0);// 包含所有元素的整个区域
            this.viewRect = new cu.Rectangle(0, 0, w, h);// canvas所显示的区域
            this.scrollbar = new cu.Scrollbar(w, h, this.elmsRect, this.viewRect);

            this.canvas = this.createCanvas(w, h);
            this.div = this.createPanelDiv(this.canvas, w, h);
            this.context = this.canvas.getContext('2d');
            this.layers = [];// 先放入的位于底层
            this.layers.push(new cu.Layer());

            this.addEventListeners();

            this.elmHover = null;
            this.hoverPointP1 = null;
            this.hoverPointP2 = null;
            this.elmSelected = null;

            this.isElmInvalidateLocked = false;
            this.isHandToolControlled = false;
            this.isPressed = false;
        },
        createCanvas: function(w, h) {
            var cvs = document.createElement('canvas');
            var st = cvs.style;
            st.position = 'absolute';
            //st.background = "#CCE8CF";
            cvs.width = w;
            cvs.height = h;
            return cvs;
        },
        createPanelDiv: function(canvas, w, h) {
            var div = document.createElement('div');
            var st = div.style;
            st.width = w + 'px';
            st.height = h + 'px';
            st.position = 'relative';
            st.overflow = 'hidden';
            st.fontSize = "12px";
            st.fontFamily = "arial, tahoma, sans-serif, helvetica";
            st.cursor = "default";
            div.onmousedown = cu.Event.preventDefault;
            div.appendChild(canvas);
            return div;
        },
        getWidth: function() {
            return this.canvas.width;
        },
        getHeight: function() {
            return this.canvas.height;
        },
        setWidth: function(w) {
            if (this.canvas.width === w)    return;
            this.div.style.width = w + 'px';
            this.canvas.width = w;
            this.scrollbar.width = w;
            var vr = this.viewRect;
            var er = this.elmsRect;
            vr.w = w / this.scale;
            if (vr.w >= er.w) {
                vr.x = 0;
                vr.update();
                this.isAllDirty = true;
            } else {
                var r = vr.x + vr.w;
                if (r > er.r) {
                    vr.r = er.r + this.scrollbar.barWidth;
                    vr.update(cu.Rectangle.BASE_RIGTHBOTTOM);
                    this.isAllDirty = true;
                }
            }
            this.scrollbar.update();
        },
        setHeight: function(h) {
            if (this.canvas.height === h)   return;
            this.div.style.height = h + 'px';
            this.canvas.height = h;
            this.scrollbar.height = h;
            var vr = this.viewRect;
            var er = this.elmsRect;
            vr.h = h / this.scale;
            if (vr.h >= er.h) {
                vr.y = 0;
                vr.update();
                this.isAllDirty = true;
            } else {
                var b = vr.y + vr.h;
                if (b > er.b) {
                    vr.b =er.b + this.scrollbar.barWidth;
                    vr.update(cu.Rectangle.BASE_RIGTHBOTTOM);
                    this.isAllDirty = true;
                }
            }
            this.scrollbar.update();
        },
        setScale: function(scale) {
            if (scale <= 0 || scale > 2)    throw 'scale must be in (0,2)';
            this.scale = scale;
            var vr = this.viewRect;
            var er = this.elmsRect;
            vr.w = this.canvas.width / scale;
            vr.h = this.canvas.height / scale;
            vr.update(vr.r === er.r || vr.b === er.b ? cu.Rectangle.BASE_RIGTHBOTTOM : null);
            if (vr.r > er.r || vr.b > er.b) {
                vr.r = vr.r > er.r ? er.r : vr.r;
                vr.b = vr.b > er.b ? er.b : vr.b;
                vr.update(cu.Rectangle.BASE_RIGTHBOTTOM);
            }
            this.scrollbar.update();
            this.isAllDirty = true;
        },
        getFitPanelScale: function() {
            var a = this.canvas.width / this.elmsRect.w;
            var b = this.canvas.height / this.elmsRect.h;
            a = a < b ? a : b;
            return a;
        },
        setHandToolEnabled: function(bool) {
            this.isHandToolControlled = bool;
            this.canvas.style.cursor = bool ? 'hand' : 'default';
        },
        export2PngBase64: function() {
            var er = this.elmsRect;
            var cvs = this.createCanvas(er.w, er.h);
            var c = cvs.getContext('2d');
            for (var i=-1, len=this.layers.length; ++i<len;) {
                this.layers[i].invalidate(c, this.elmsRect);
            }
            var data = cvs.toDataURL('image/png');
            var i = data.indexOf(',');
            return data.slice(i+1);
        },
        setStyle: function(k, v) {
            var st = this.div.style;
            st[k] = v;
        },
        addElement: function(v) {
            v.panel = this;
            if (!v.layer) {
                v.layer = this.layers[0];
                this.layers[0].add(v);
            } else {
                v.layer.add(v);
            }
            v.calcRect();
            this.dirtyRect.add(v.rect);
            v.dirtyRect.reset();
            v.on(cu.Event.INVALIDATE, this.proxy(this.handleElementInvalidate));
        },
        empty: function() {
            for (var i=-1, len=this.layers.length; ++i<len;) {
                this.layers[i].empty();
            }
            this.elmHover = null;
            this.hoverPointP1 = null;
            this.hoverPointP2 = null;
            this.elmSelected = null;
            this.isScrollbarHovered = false;
            var er = this.elmsRect;
            var vr = this.viewRect;
            er.w = er.h = 0;
            er.update();
            vr.w = this.canvas.width;
            vr.h = this.canvas.height;
            vr.update();
            this.isElmInvalidateLocked = false;
            this.setHandToolEnabled(false);
            this.isPressed = false;
            this.scale = 1;
            this.scrollbar.update();
            this.isAllDirty = true;
            this.invalidate();
        },
        handleElementInvalidate: function(e) {
            this.dirtyRect.add(e.target.dirtyRect);
            e.target.layer.update(e.target);
            if (!this.isElmInvalidateLocked) {
                this.invalidate();
            }
        },
        addLayer: function(layer) {
            this.layers.push(layer);
        },
        lockElementInvalidate: function() {
            this.isElmInvalidateLocked = true;
        },
        invalidate: function() {
            this.isElmInvalidateLocked = false;
            if (this.calcElmsRect()) {
                this.scrollbar.update();
                this.isAllDirty = true;
            }
            this.draw();
        },
        calcElmsRect: function() {
            var r = 0;
            var b = 0;
            var er = this.elmsRect;
            var vr = this.viewRect;
            for (var i=-1, len=this.layers.length; ++i<len;) {
                var v = this.layers[i];
                v.updateQuadTree();
                r = v.rect.r > r ? v.rect.r : r;
                b = v.rect.b > b ? v.rect.b : b;
            }
            var vrChanged = false;
            if (er.w !== r) {
                er.w = r;
                er.update();
                if (vr.w >= er.w && vr.x !== 0) {
                    vr.x = 0;
                    vr.update();
                    vrChanged = true;
                } else {
                    var r = vr.x + vr.w;
                    if (r > er.r) {
                        vr.r = er.r + this.scrollbar.barWidth;
                        vr.update(cu.Rectangle.BASE_RIGTHBOTTOM);
                        vrChanged = true;
                    }
                }
            }
            if (er.h !== b) {
                er.h = b;
                er.update();
                if (vr.h >= er.h && vr.y !== 0) {
                    vr.y = 0;
                    vr.update();
                    vrChanged = true;
                } else {
                    var b = vr.y + vr.h;
                    if (b > er.b) {
                        vr.b = er.b + this.scrollbar.barWidth;
                        vr.update(cu.Rectangle.BASE_RIGTHBOTTOM);
                        vrChanged = true;
                    }
                }
            }
            return vrChanged;
        },
        draw: function() {
            var c = this.context;
            var vr = this.viewRect;
            var scale = this.scale;
//            var r = vr;
            if (this.isAllDirty) {
                var r = vr;
                this.isAllDirty = false;
            } else {
                var r = this.dirtyRect.intersection(this.viewRect);
            }

            c.save();
            c.scale(scale, scale);
            c.translate(-vr.x, -vr.y);
            c.clearRect(r.x-1, r.y-1, r.w+2, r.h+2);
            c.beginPath();
            c.rect(r.x-2, r.y-2, r.w+4, r.h+4);
            c.clip();
            for (var i=-1, len=this.layers.length; ++i<len;) {
                this.layers[i].invalidate(c, r);
            }
            c.restore();
            this.dirtyRect.reset();

            this.scrollbar.draw(c);
        },
        findElement: function(id) {
            var layer = null;
            var elm = null;
            for (var i=-1, lenI=this.layers.length; ++i<lenI;) {
                layer = this.layers[i];
                for (var j=-1, lenJ=layer.elms.length; ++j<lenJ;) {
                    elm = layer.elms[j];
                    if (id === elm.id) {
                        return elm;
                    }
                }
            }
        },
        windowPoint2CanvasPoint: function(x, y) {
            var bbox = this.canvas.getBoundingClientRect();
            return { x: x - bbox.left * (this.canvas.width / bbox.width),
                y: y - bbox.top * (this.canvas.height / bbox.height)};
        },
        canvasPoint2windowPoint: function(x, y) {
            var bbox = this.canvas.getBoundingClientRect();
            return { x: x + bbox.left * (this.canvas.width / bbox.width),
                y: y + bbox.top * (this.canvas.height / bbox.height)};
        },
        viewRectPoint2ElmsPoint: function(x, y) {
            return {x: x / this.scale + this.viewRect.x, y: y / this.scale + this.viewRect.y};
        },
        elmsPoint2ViewRectPoint: function(x, y) {
            return {x: (x - this.viewRect.x) * this.scale, y: (y - this.viewRect.y) * this.scale};
        },
        addEventListeners: function() {
            var f = cu.Event.addEventListener;
            f(this.div, cu.Event.CLICK, this.proxy(this.onClick));
            f(this.div, cu.Event.DBCLICK, this.proxy(this.onDblClick));
            f(this.div, cu.Event.MOUSEDOWN, this.proxy(this.onMousedown));
            f(this.div, cu.Event.MOUSEUP, this.proxy(this.onMouseup));
            f(this.div, cu.Event.MOUSEMOVE, this.proxy(this.onMousemove));
            f(this.div, cu.Event.MOUSEOVER, this.proxy(this.onMouseover));
            f(this.div, cu.Event.MOUSEOUT, this.proxy(this.onMouseout));
            f(this.div, cu.Event.MOUSEWHEEL, this.proxy(this.onMouseWheel));
            f(document, cu.Event.MOUSEMOVE, this.proxy(this.onMousemoveOutside));
            f(document, cu.Event.MOUSEUP, this.proxy(this.onMouseupOutside));

            this.scrollbar.on(cu.Event.SCROLL, this.proxy(this.onScroll));
        },
        onMouseWheel: function(e) {
            var e = window.event || e; // old IE support
            var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
            this.scrollbar.scroll(null, -delta * 10);
            return false;
        },
        onClick: function(e) {
            var p1 = this.windowPoint2CanvasPoint(e.clientX, e.clientY);
            var p2 = this.viewRectPoint2ElmsPoint(p1.x, p1.y);
            if (this.elmHover && this.elmHover.isSelectable) {
                this.elmHover.handleOn(cu.Event.CLICK, e, p1, p2);
            } else {

            }
            this.handleOn(cu.Event.CLICK, e, p1, p2);
            return false;
        },
        onDblClick: function(e) {
            var p1 = this.windowPoint2CanvasPoint(e.clientX, e.clientY);
            var p2 = this.viewRectPoint2ElmsPoint(p1.x, p1.y);
            if (this.elmHover && this.elmHover.isSelectable) {
                this.elmHover.handleOn(cu.Event.DBCLICK, e, p1, p2);
            } else {
            }
            this.handleOn(cu.Event.DBCLICK, e, p1, p2);
            return false;
        },
        onMousedown: function(e) {
            var p1 = this.hoverPointP1 = this.windowPoint2CanvasPoint(e.clientX, e.clientY);
            var p2 = this.hoverPointP2 = this.viewRectPoint2ElmsPoint(p1.x, p1.y);
            if (this.isScrollbarHovered) {
                this.scrollbar.handleOn(cu.Event.MOUSEDOWN, e, p1, p2);
                return;
            }

            if (this.isHandToolControlled) {
                this.isPressed = true;
                return;
            }
            if (this.elmHover) {
                if (this.elmHover.isSelectable) {
                    this.elmHover.handleOn(cu.Event.MOUSEDOWN, e, p1, p2);
                    if (!this.elmSelected) {
                        this.elmSelected = this.elmHover;
                        this.elmSelected.layer.increaseElmIndex(this.elmSelected);
                        this.elmSelected.handleOn(cu.Event.FOCUS, e, p1, p2);
                    } else if (this.elmSelected !== this.elmHover) {
                        this.elmSelected.handleOn(cu.Event.BLUR, e, p1, p2);
                        this.elmSelected = this.elmHover;
                        this.elmSelected.layer.increaseElmIndex(this.elmSelected);
                        this.elmSelected.handleOn(cu.Event.FOCUS, e, p1, p2);
                    }
                } else {
/*                    if (this.elmSelected) {
                        this.elmSelected.handleOn(cu.Event.BLUR, e, p1, p2);
                        this.elmSelected = null;
                    }*/
                }
            } else {
/*                if (this.elmSelected) {
                    this.elmSelected.handleOn(cu.Event.BLUR, e, p1, p2);
                    this.elmSelected = null;
                }*/
            }
            this.handleOn(cu.Event.MOUSEDOWN, e, p1, p2);
            return false;
        },
        onMouseup: function(e) {
            if (this.isScrollbarHovered) {
                this.scrollbar.handleOn(cu.Event.MOUSEUP, e);
            } else if (this.isHandToolControlled) {
                this.isPressed = false;
            } else if (this.elmHover) {
                this.elmHover.handleOn(cu.Event.MOUSEUP, e);
            }
            this.handleOn(cu.Event.MOUSEUP, e, null, null);
            return false;
        },
        onMousemove: function(e) {
            var p1 = this.windowPoint2CanvasPoint(e.clientX, e.clientY);
            var p2 = this.viewRectPoint2ElmsPoint(p1.x, p1.y);
            var oldHoverPointP1 = this.hoverPointP1;
            var oldHoverPointP2 = this.hoverPointP2;
            this.hoverPointP1 = p1;
            this.hoverPointP2 = p2;

            if (this.isScrollbarHovered && this.scrollbar.isPressed) {
                this.scrollbar.handleOn(cu.Event.MOUSEMOVE, e, p1, p2);
                return;
            }

            if (this.scrollbar.isOnHandle(p1.x, p1.y)) {
                if (!this.isScrollbarHovered) {
                    this.scrollbar.handleOn(cu.Event.MOUSEOVER, e, p1, p2);
                    this.isScrollbarHovered = true;
                }
                this.scrollbar.handleOn(cu.Event.MOUSEMOVE, e, p1, p2);
                return;
            } else {
                if (this.isScrollbarHovered) {
                    this.isScrollbarHovered = false;
                    this.scrollbar.handleOn(cu.Event.MOUSEOUT, e, p1, p2);
                }
            }

            if (this.isHandToolControlled) {
                if (this.isPressed) {
                    var deltaX = (p1.x - oldHoverPointP1.x) / this.scale;
                    var deltaY = (p1.y - oldHoverPointP1.y) / this.scale;
                    var vr = this.viewRect;
                    var er = this.elmsRect;
                    var x = vr.x - deltaX;
                    var y = vr.y - deltaY;
                    x = x < 0 ? 0 : x;
                    y = y < 0 ? 0 : y;
                    var a = er.r - vr.w + this.scrollbar.barWidth;
                    a = a < 0 ? 0 : a;
                    x = x > a ? a : x;
                    a = er.b - vr.h + this.scrollbar.barWidth;
                    a = a < 0 ? 0 : a;
                    y = y > a ? a : y;
                    vr.x = x;
                    vr.y = y;
                    vr.update();
                    this.scrollbar.update();
                    this.isAllDirty = true;
                    this.invalidate();
                }
                return;
            }

            if (this.elmHover && this.elmHover.isPressed) {
                this.elmHover.handleOn(cu.Event.MOUSEMOVE, e, p1, p2);
            } else {
                var elmHover = null;
                for (var i=this.layers.length, lenI=-1; --i>lenI;) {
                    var layer = this.layers[i];
                    var elms = layer.findElementsOnPoint(p2.x, p2.y);
                    for (var j=elms.length, lenJ=-1; --j>lenJ;) {
                        var elm = elms[j];
                        if (elm.isHoverable && elm.canHover()) {
                            elmHover = elm;
                            if (!this.elmHover) {
                                this.elmHover = elmHover;
                                elmHover.handleOn(cu.Event.MOUSEOVER, e, p1, p2);
                            } else if (this.elmHover !== elmHover) {
                                this.elmHover.handleOn(cu.Event.MOUSEOUT, e, p1, p2);
                                this.elmHover = elmHover;
                                elmHover.handleOn(cu.Event.MOUSEOVER, e, p1, p2);
                            }
                            elmHover.handleOn(cu.Event.MOUSEMOVE, e, p1, p2);
                            break;
                        }
                    }
                    if (elmHover) {
                        break;
                    }
                }
                if (!elmHover) {
                    // 移动到空白
                    if (this.elmHover) {
                        this.elmHover.handleOn(cu.Event.MOUSEOUT, e, p1, p2);
                        this.elmHover = null;
                    }
                }
            }
            return false;
        },
        onMouseover: function(e) {
            this.handleOn(cu.Event.MOUSEOVER, e, null, null);
            return false;
        },
        onMouseout: function(e) {
/*            if (this.elmHover) {
                if (this.elmHover.isPressed) {
                    return;
                }
                this.elmHover.handleOn(cu.Event.MOUSEOUT, e);
                this.hoverPointP1 = null;
                this.hoverPointP2 = null;
                this.elmHover = null;
            }*/
            cu.tip.hide();
            this.handleOn(cu.Event.MOUSEOUT, e, null, null);
            return false;
        },
        onScroll: function(e) {
            this.isAllDirty = true;
            this.invalidate();
        },
        onMousemoveOutside: function(e) {
            if (this.isScrollbarHovered && this.scrollbar.isPressed) {
                var p1 = this.windowPoint2CanvasPoint(e.clientX, e.clientY);
                this.scrollbar.handleOn(cu.Event.MOUSEMOVE, e, p1, null);
            }
        },
        onMouseupOutside: function(e) {
            if (this.isScrollbarHovered && this.scrollbar.isPressed) {
                this.scrollbar.handleOn(cu.Event.MOUSEUP, e);
            }
        }
    });

    cu.Tip = cu.createClass();
    cu.Tip.include({
        init: function() {
            this.divTip = this.getTipDiv();
            this.appended = false;
            this.timerId = null;
        },
        draw: function(context, x, y, mw, mh, text) {//mw mh是canvas元素当前宽长
            y+=18;
            var metric = cu.g.measureText(text);
            var textWidth = metric.width;
            if (textWidth > cu.Tip.MAX_TEXT_WIDTH) {
                textWidth = cu.Tip.MAX_TEXT_WIDTH;
            }
            var lineCount = Math.ceil(metric.width / cu.Tip.MAX_TEXT_WIDTH);
            var lineCharCount = Math.round(text.length * cu.Tip.MAX_TEXT_WIDTH / metric.width);
            var width = Math.round(textWidth + 2*(this.padding + this.outlineWidth));
            var height = Math.round(this.fontSize*(lineCount+1) + this.padding*lineCount + 2*(this.padding + this.outlineWidth));

            x = Math.round(x+width > mw ? mw-width : x) - 0.5;
            y = Math.round(y+height > mh ? mh-height : y) - 0.5;

            this.imgData = context.getImageData(x, y, width, height);
            this.dx = x;
            this.dy = y;

            cu.drawRoundRect(context, x+this.outlineWidth/2, y+this.outlineWidth/2,
                width-this.outlineWidth, height-this.outlineWidth, 5);
            context.save();
            context.strokeStyle = '#000000';
            context.fillStyle = '#FFFFE0';
            context.stroke();
            context.fill();
            context.fillStyle = '#000000';
            context.textAlign = "left";
            context.textBaseline = "top";

            var offSetX = x + this.padding;
            var offSetY = y + this.padding;
            for (var i=0, j=0, len=text.length; i<=lineCount; i++) {
                var k = j + lineCharCount;
                if (k > len) {
                    k = len;
                }
                context.fillText(text.substring(j, k), offSetX, offSetY, cu.Tip.MAX_TEXT_WIDTH);
                offSetY += (this.fontSize + this.padding);
                j=k;
            }

            context.restore();
        },
        getTipDiv: function() {
            var div = document.createElement("div");
            div.className = "tp-tooltip";
            return div;
        },
        show: function(text, panel, p1) {
            if (this.appended) {
                win.appUtils.getPageContainer()[0].removeChild(this.divTip);
                this.appended = false;
            }
            this.timerId = win.setTimeout(this.proxy(function() {
                var p = panel.canvasPoint2windowPoint(p1.x, p1.y);
                var div = this.divTip;
                div.innerHTML = text;
                div.style.left = p.x + 'px';
                div.style.top = p.y + 18 + 'px';
                win.appUtils.getPageContainer()[0].appendChild(div);
                this.appended = true;
            }), cu.Tip.DELAY);
        },
        hide: function() {
            if (this.timerId !== -1) {
                win.clearTimeout(this.timerId);
                this.timerId = -1;
            }
            if (this.appended) {
                win.appUtils.getPageContainer()[0].removeChild(this.divTip);
                this.appended = false;
            }
        }
    });
    cu.Tip.extend({
        DELAY: 500,
        MAX_TEXT_WIDTH: 160
    });
    cu.tip = new cu.Tip();

    cu.Element = cu.createClass(cu.EventListener);
    cu.Element.include({
        init: function(id) {
            cu.Element._super.fn.init.call(this);
            this.id = id ? id : cu.guid();
            this.panel = null;
            this.layer = null;
            this.indexInLayer = null;
            this.rect = new cu.Rectangle(0, 0, 0, 0);
            this.dirtyRect = new cu.DirtyRect();
            this.style = {};
            this.customData = {};
            this.isHoverable = true;
            this.isSelectable = true;
            this.isDraggable = true;
            this.hoverPointP1 = null;
            this.hoverPointP2 = null;
            this.isPressed = false;
            this.isFoucus = false;

            this.isDisplayChanged = true;
            this.on(cu.Event.CLICK, this.proxy(this.onClick));
            //this.on(cu.Event.DBCLICK, this.proxy(this.onDblClick));
            this.on(cu.Event.MOUSEDOWN, this.proxy(this.onMousedown));
            this.on(cu.Event.MOUSEUP, this.proxy(this.onMouseup));
            this.on(cu.Event.MOUSEMOVE, this.proxy(this.onMousemove));
            this.on(cu.Event.MOUSEOVER, this.proxy(this.onMouseover));
            this.on(cu.Event.MOUSEOUT, this.proxy(this.onMouseout));
            this.on(cu.Event.FOCUS, this.proxy(this.onFocus));
            this.on(cu.Event.BLUR, this.proxy(this.onBlur));
        },
        setLocation: function(x, y) {
            var r = this.rect;
            this.dirtyRect.add(r);
            this.doCalcRect();
            var oldX = r.x;
            var oldY = r.y;
            r.x = x;
            r.y = y;
            this.rect.update(cu.Rectangle.BASE_LEFTUP);
            if (r.x !== oldX || r.y !== oldY) {
                this.onLocationChanged(r.x-oldX, r.y-oldY);
            }
            this.dirtyRect.add(r);
        },
        setCenterLocation: function(x, y) {
            var r = this.rect;
            this.dirtyRect.add(r);
            this.doCalcRect();
            var oldX = r.x;
            var oldY = r.y;
            r.cx = x;
            r.cy = y;
            r.update(cu.Rectangle.BASE_CENTER);
            if (r.x !== oldX || r.y !== oldY) {
                this.onLocationChanged(r.x-oldX, r.y-oldY);
            }
            this.dirtyRect.add(r);
        },
        setLayer: function(layer) {
            if (this.layer) {
                this.layer.remove(this);
                this.layer = layer;
                layer.add(this);
            } else {
                this.layer = layer;
            }
        },
        setStyle: function(k, v) {
            var st = this.style;
            var o = st[k];
            if (o !== v) {
                st[k] = v;
                this.isDisplayChanged = true;
            }
        },
        calcRect: function() {
            if (this.isDisplayChanged) {
                this.doCalcRect();
                this.isDisplayChanged = false;
            }
        },
        doCalcRect: function() {
        },
        invalidate: function() {
            this.calcRect();
            this.dirtyRect.add(this.rect);
            this.handleOn('invalidate');
            this.dirtyRect.reset();
        },
        draw: function(context) {
        },
        canHover: function() {
            return true;
        },
        onClick: function(e) {
            logger.debug('elm click');
        },
        onDblClick: function(e) {
            logger.debug('elm dbclick');
        },
        onMousedown: function(e) {
            logger.debug('elm mousedown');
            this.isPressed = true;
        },
        onMouseup: function(e) {
            logger.debug('elm mouseup');
            this.isPressed = false;
        },
        onMousemove: function(e) {
            if (this.isPressed && this.isDraggable) {
                var dx = e.p2.x - this.hoverPointP2.x;
                var dy = e.p2.y - this.hoverPointP2.y;
                this.hoverPointP1.x = e.p1.x;
                this.hoverPointP1.y = e.p1.y;
                this.hoverPointP2.x = e.p2.x;
                this.hoverPointP2.y = e.p2.y;
                var newX = this.rect.x + dx;
                var newY = this.rect.y + dy;
                newX = newX < 0 ? 0 : newX;
                newY = newY < 0 ? 0 : newY;
                this.setLocation(newX, newY);
                this.invalidate();
            } else {
                this.hoverPointP1.x = e.p1.x;
                this.hoverPointP1.y = e.p1.y;
                this.hoverPointP2.x = e.p2.x;
                this.hoverPointP2.y = e.p2.y;
            }
        },
        onMouseover: function(e) {
            this.hoverPointP1 = e.p1;
            this.hoverPointP2 = e.p2;
            logger.debug('mouseover');
        },
        onMouseout: function(e) {
            this.hoverPointP1 = null;
            this.hoverPointP2 = null;
            logger.debug('mouseout');
        },
        onFocus: function(e) {
            logger.debug('focus');
            this.isFoucus = true;

        },
        onBlur: function(e) {
            logger.debug('blur');
            this.isFoucus = false;

        },
        onLocationChanged: function(dx, dy) {
        },
        data: function(k, v) {
            if (v) {
                this.customData[k] = v;
                return v;
            } else if (k) {
                return this.customData[k];
            } else {
                return this.customData;
            }
        }
    });

    cu.Scrollbar = cu.createClass(cu.EventListener);
    cu.Scrollbar.include({
        init: function(w, h, elmsRect, viewRect) {
            cu.Scrollbar._super.fn.init.call(this);
            this.barWidth = 8;
            this.cornerRadious = 4;
            this.width = w;
            this.height = h;

            this.elmsRect = elmsRect;
            this.viewRect = viewRect;
            this.rectX = null;
            this.rectY = null;

            this.offsetX = 0;// x轴偏移量
            this.offsetY = 0;
            this.OFFSET_MAX_X = null;
            this.OFFSET_MAX_Y = null;

            this.handleSizeX = null;
            this.handleSizeY = null;

            this.isSelectable = false;
            this.isPressed = false;
            this.hoverType = 0;// 1表示x轴,2表示y轴
            this.hoverPointP1 = null;

            this.isVisible = true;
            this.isXScrollable = false;
            this.isYScrollable = false;
            if (elmsRect.w > 0 && elmsRect.h > 0) {
                this.update();
            } else {
                this.isXScrollable = false;
                this.isYScrollable = false;
            }

            this.on(cu.Event.MOUSEDOWN, this.proxy(this.onMousedown));
            this.on(cu.Event.MOUSEUP, this.proxy(this.onMouseup));
            this.on(cu.Event.MOUSEMOVE, this.proxy(this.onMousemove));
            this.on(cu.Event.MOUSEOVER, this.proxy(this.onMouseover));
            this.on(cu.Event.MOUSEOUT, this.proxy(this.onMouseout));

        },
        update: function() {
            var elmsRect = this.elmsRect;
            var viewRect = this.viewRect;
            var vrW = viewRect.w;
            var vrH = viewRect.h;
            var erW = elmsRect.w;
            var erH = elmsRect.h;
            var bw = this.barWidth;
            this.rectX = new cu.Rectangle(0, this.height - bw, this.width - bw, bw);
            this.rectY = new cu.Rectangle(this.width - bw, 0, bw, this.height - bw);

            if (elmsRect.w > viewRect.w) {
                this.isXScrollable = true;
                this.handleSizeX = vrW / erW * this.rectX.w;
                this.OFFSET_MAX_X = this.rectX.w - this.handleSizeX;
                this.offsetX = viewRect.x / (elmsRect.w + bw - viewRect.w) * this.OFFSET_MAX_X;
            } else {
                this.isXScrollable = false;
            }

            if (elmsRect.h > viewRect.h) {
                this.isYScrollable = true;
                this.handleSizeY = vrH / erH * this.rectY.h;
                this.OFFSET_MAX_Y = this.rectY.h - this.handleSizeY;
                this.offsetY = viewRect.y / (elmsRect.h + bw - viewRect.h) * this.OFFSET_MAX_Y;
            } else {
                this.isYScrollable = false;
            }
        },
        isOnHandle: function(x, y) {
            if (this.rectX && this.rectX.isIn(x, y)) {
                this.hoverType = (x > this.offsetX && (x - this.offsetX) < this.handleSizeX) ? 1 : 0;
            } else if (this.rectY && this.rectY.isIn(x, y)){
                this.hoverType = (y > this.offsetY && (y - this.offsetY) < this.handleSizeY) ? 2 : 0;
            } else {
                this.hoverType = 0;
                return false;
            }
            return this.hoverType !== 0;
        },
        draw: function(context) {
            if (!this.isVisible) {
                return;
            } else {
                context.save();
                if (this.isXScrollable && this.rectX.w > 60) {
                    this.drawScrollBarX(context);
                }
                if (this.isYScrollable && this.rectY.h > 60) {
                    this.drawScrollBarY(context);
                }
                context.restore();
            }
        },
        drawScrollBarX: function(context) {
            var rX = this.rectX;
            var rad = this.cornerRadious;
            cu.drawRoundRect(context, rX.x, rX.y, rX.w, rX.h, rad);
            //context.fillStyle = '#818181';
            context.fillStyle = '#E0E1E1';
            //context.globalAlpha = 0.2;
            context.fill();

            cu.drawRoundRect(context, rX.x + this.offsetX, rX.y, this.handleSizeX, rX.h, rad);
            var gradient = context.createLinearGradient(0, rX.y, 0, rX.b);
            gradient.addColorStop(0, '#E0E1E1');
            gradient.addColorStop(1, '#818181');
            context.fillStyle = gradient;
            //context.globalAlpha = 1;
            context.fill();
        },
        drawScrollBarY: function(context) {
            var rY = this.rectY;
            var rad = this.cornerRadious;
            cu.drawRoundRect(context, rY.x, rY.y, rY.w, rY.h, rad);
            //context.fillStyle = '#818181';
            context.fillStyle = '#E0E1E1';
            //context.globalAlpha = 0.2;
            context.fill();

            cu.drawRoundRect(context, rY.x, rY.y + this.offsetY, rY.w, this.handleSizeY, rad);
            var gradient = context.createLinearGradient(rY.x, 0, rY.r, 0);
            gradient.addColorStop(0, '#E0E1E1');
            gradient.addColorStop(1, '#818181');
            context.fillStyle = gradient;
            //context.globalAlpha = 1;
            context.fill();
        },
        onMousedown: function(e) {
            this.isPressed = true;
        },
        onMouseup: function(e) {
            this.isPressed = false;
        },
        onMouseover: function(e) {
            this.hoverPointP1 = e.p1;
        },
        onMouseout: function(e) {
            this.hoverPointP1 = null;
        },
        onMousemove: function(e) {
            if (this.isPressed) {
                if (this.hoverType === 1) {
                    var dx = e.p1.x - this.hoverPointP1.x;
                    var x = this.offsetX + dx;
                    x = x < 0 ? 0 : x;
                    x = x > this.OFFSET_MAX_X ? this.OFFSET_MAX_X : x;
                    this.offsetX = x;
                    this.viewRect.x = (this.offsetX / this.OFFSET_MAX_X) * (this.elmsRect.w + this.barWidth - this.viewRect.w);
                } else if (this.hoverType === 2){
                    var dy = e.p1.y - this.hoverPointP1.y;
                    var y = this.offsetY + dy;
                    y = y < 0 ? 0 : y;
                    y = y > this.OFFSET_MAX_Y ? this.OFFSET_MAX_Y : y;
                    this.offsetY = y;
                    this.viewRect.y = (this.offsetY / this.OFFSET_MAX_Y) * (this.elmsRect.h + this.barWidth - this.viewRect.h);
                }
                this.viewRect.update();
                this.handleOn('scroll');
            }
            this.hoverPointP1.x = this.offsetX !== 0 && this.offsetX !== this.OFFSET_MAX_X ? e.p1.x : this.hoverPointP1.x;
            this.hoverPointP1.y = this.offsetY !== 0 && this.offsetY !==  this.OFFSET_MAX_Y ? e.p1.y : this.hoverPointP1.y;
        },
        scroll: function(dx, dy) {
            var did = false;
            if (this.isXScrollable && dx) {
                did = true;
                var x = this.offsetX + dx;
                x = x < 0 ? 0 : x;
                x = x > this.OFFSET_MAX_X ? this.OFFSET_MAX_X : x;
                this.offsetX = x;
                this.viewRect.x = (this.offsetX / this.OFFSET_MAX_X) * (this.elmsRect.w + this.barWidth - this.viewRect.w);
                this.viewRect.update();
            }
            if (this.isYScrollable && dy) {
                did = true;
                var y = this.offsetY + dy;
                y = y < 0 ? 0 : y;
                y = y > this.OFFSET_MAX_Y ? this.OFFSET_MAX_Y : y;
                this.offsetY = y;
                this.viewRect.y = (this.offsetY / this.OFFSET_MAX_Y) * (this.elmsRect.h + this.barWidth - this.viewRect.h);
                this.viewRect.update();
            }
            if (did) {
                this.handleOn('scroll');
            }
        }
    });

    cu.Node = cu.createClass(cu.Element);
    cu.Node.include({
        init: function(id, imageName, name) {
            cu.Node._super.fn.init.call(this, id);

            var s = cu.Style;
            this.setStyle(s.BACKGROUND_COLOR, '#FFFFFF');
            this.setStyle(s.PADDING, 2);
            this.setStyle(s.MARGIN, 2);
            this.setStyle(s.OUTLINE_WIDTH, 0);
            this.setStyle(s.SHAPE, 'circle');
            this.setStyle(s.LABEL_DIRECTION, 'bottom');
            this.setStyle(s.LABEL_GAP, 5);

            this.icon = this.createIcon(_images[imageName]);
            this.label = this.createLabel(name);

            this.title = name ? name : null;
        },
        createIcon: function(image) {
            return new cu.NodeIcon(null, image);
        },
        createLabel: function(text) {
            return new cu.Label(null, text);
        },
        setImageName: function(name) {
            this.icon.setImage(_images[name]);
        },
        setName: function(name) {
            this.title = name;
            this.label.setText(name);
        },
        setIconCenterLocation: function(x, y) {
            switch (this.style[cu.Style.LABEL_DIRECTION]) {
                case 'top':
                    this.setCenterLocation(x, y - (this.icon.rect.cy - this.rect.cy));
                    break;
                case 'bottom':
                    this.setCenterLocation(x, y + (this.rect.cy - this.icon.rect.cy));
                    break;
                case 'left':
                    this.setCenterLocation(x - (this.icon.rect.cx - this.rect.cx), y);
                    break;
                case 'right':
                    this.setCenterLocation(x + (this.rect.cx - this.icon.rect.cx), y);
                    break;
            }
        },
        doCalcRect: function() {
            var r = this.rect;
            var margin = this.style[cu.Style.MARGIN];
            var olw = this.style[cu.Style.OUTLINE_WIDTH];
            var padding = this.style[cu.Style.PADDING];
            var gap = this.style[cu.Style.LABEL_GAP];
            var extra = padding + olw + margin;

            this.icon.calcRect();
            var rIcon = this.icon.rect;

            this.label.calcRect();
            var rLabel = this.label.rect;

            gap = rLabel.w === 0 ? 0 : gap;
            switch (this.style[cu.Style.LABEL_DIRECTION]) {
                case 'top':
                    r.w = (rIcon.w > rLabel.w ? rIcon.w : rLabel.w) + 2 * extra
                    r.h = rIcon.h + gap + rLabel.h + 2 * extra;
                    r.update();

                    this.icon.setLocation(r.cx - rIcon.w / 2, r.y + extra + rLabel.h + gap);
                    this.label.setLocation(r.cx - rLabel.w / 2, r.y + extra);
                    break;
                case 'bottom':
                    r.w = (rIcon.w > rLabel.w ? rIcon.w : rLabel.w) + 2 * extra
                    r.h = rIcon.h + gap + rLabel.h + 2 * extra;
                    r.update();

                    this.icon.setLocation(r.cx - rIcon.w / 2, r.y + extra);
                    this.label.setLocation(r.cx - rLabel.w / 2, r.y + extra + rIcon.h + gap);
                    break;
                case 'left':
                    r.w = rIcon.w + gap + rLabel.w + 2 * extra;
                    r.h = (rIcon.h > rLabel.h ? rIcon.h : rLabel.h) + 2 * extra
                    r.update();

                    this.icon.setLocation(r.x + extra + rLabel.w + gap, r.cy - rIcon.h / 2);
                    this.label.setLocation(r.x + extra, r.cy - rLabel.h / 2);
                    break;
                case 'right':
                    r.w = rIcon.w + gap + rLabel.w + 2 * extra;
                    r.h = (rIcon.h > rLabel.h ? rIcon.h : rLabel.h) + 2 * extra
                    r.update();

                    this.icon.setLocation(r.x + extra, r.cy - rIcon.h / 2);
                    this.label.setLocation(r.x + extra + rIcon.w + gap, r.cy - rLabel.h / 2);
                    break;
            }
        },
        onLocationChanged: function(dx, dy) {
            this.icon.rect.deltaLocation(dx, dy);
            this.label.rect.deltaLocation(dx, dy);
        },
        draw: function(context) {
            this.drawHighlightArea(context);
            this.icon.draw(context);
            this.label.draw(context);
        },
        drawHighlightArea: function(context) {
            var r = this.rect;
            cu.drawRoundRect(context, r.x, r.y, r.w, r.h, 3);
            context.save();
            if (this.hoverPointP2) {
                context.fillStyle = '#B9D7FC';
                context.globalAlpha = 0.8;
            } else {
                context.globalAlpha = 0;
            }
            context.fill();
            context.restore();
        },
        onMouseover: function(e) {
            cu.Node._super.fn.onMouseover.call(this, e);
            this.invalidate();
            if (this.title) {
                cu.tip.show(this.title, this.panel, this.hoverPointP1);
            }
        },
        onMouseout: function(e) {
            cu.Node._super.fn.onMouseout.call(this, e);
            if (this.title) {
                cu.tip.hide();
            }
            this.invalidate();
        },
        onMousedown: function(e) {
            cu.Node._super.fn.onMousedown.call(this, e);
            if (this.title) {
                cu.tip.hide();
            }
        }
    });

    cu.Label = cu.createClass(cu.Element);
    cu.Label.include({
        init: function(id, text) {
            cu.Label._super.fn.init.call(this, id);
            this.text = text;

            var s = cu.Style;
            this.setStyle(s.BACKGROUND_COLOR, '#e5e5e5');
            this.setStyle(s.PADDING, 4);
            this.setStyle(s.MARGIN, 0);
            this.setStyle(s.OUTLINE_WIDTH, 0);
            this.setStyle(s.OUTLINE_COLOR, '#FF0000');
            this.setStyle(s.FONT, '12px arial, tahoma, sans-serif, helvetica');
            this.setStyle(s.FONT_COLOR, '#222222');
            this.setStyle(s.CORNER_RADIOUS, 4);
            this.setStyle(s.ALPHA, 1);
            //this.doCalcRect();
        },
        setText: function(text) {
            this.text = text;
            this.isDisplayChanged = true;
        },
        doCalcRect: function() {
            if (!this.text) return;
            var r = this.rect;
            cu.g.font = this.style[cu.Style.FONT];
            var textWidth = cu.g.measureText(this.text).width;
            var fontSize = 12;// TODO 字体高度得想办法通过计算获得
            var margin = this.style[cu.Style.MARGIN];
            var olw = this.style[cu.Style.OUTLINE_WIDTH];
            var padding = this.style[cu.Style.PADDING];
            var extra = padding + olw + margin;
            r.w = textWidth + 2 * extra;
            r.h = fontSize + 2 * extra;
            r.update();
        },
        draw: function(context) {
            if (!this.text) return;
            context.save();
            this.drawFrame(context);
            this.drawText(context);
            context.restore();
        },
        drawFrame: function(context) {
            var r = this.rect;
            var margin = this.style[cu.Style.MARGIN];
            var olw = this.style[cu.Style.OUTLINE_WIDTH];
            var padding = this.style[cu.Style.PADDING];
            var rad = this.style[cu.Style.CORNER_RADIOUS];
            var x = r.x + margin + olw / 2;
            var y = r.y + margin + olw / 2;
            var w = r.w - 2 * margin - olw;
            var h = r.h - 2 * margin - olw;
            cu.drawRoundRect(context, x, y , w, h, rad);

            context.globalAlpha = this.style[cu.Style.ALPHA];
            if (olw > 0) {
                context.strokeStyle = this.style[cu.Style.OUTLINE_COLOR];
                context.stroke();
            }
            context.fillStyle = this.style[cu.Style.BACKGROUND_COLOR];
            context.fill();
        },
        drawText: function(context) {
            var r = this.rect;
            context.textAlign = "center";
            context.textBaseline = "middle";
            context.fillStyle = this.style[cu.Style.FONT_COLOR];
            context.fillText(this.text, r.cx, r.cy);
        }
    });

    cu.NodeIcon = cu.createClass(cu.Element);
    cu.NodeIcon.include({
        init: function(id, image) {
            cu.NodeIcon._super.fn.init.call(this, id);
            var s = cu.Style;
            this.setStyle(s.BACKGROUND_COLOR, '#FFFFFF');
            this.setStyle(s.PADDING, 0);
            this.setStyle(s.MARGIN, 0);
            this.setStyle(s.OUTLINE_WIDTH, 3);
            this.setStyle(s.INLINE_WIDTH, 0);
            this.setStyle(s.OUTLINE_COLOR, '#FFFFFF');
            this.setStyle(s.INLINE_COLOR, '#FFFFFF');
            this.setStyle(s.SHAPE, 'circle');
            this.setStyle(s.ALPHA, 1);
            this.image = image;
        },
        setImage: function(image) {
            this.image = image;
            this.isDisplayChanged = true;
        },
        doCalcRect: function() {
            var r = this.rect;
            var margin = this.style[cu.Style.MARGIN];
            var olw = this.style[cu.Style.OUTLINE_WIDTH];
            var ilw = this.style[cu.Style.INLINE_WIDTH];
            var padding = this.style[cu.Style.PADDING];
            var extra = padding + ilw + olw + margin;
            if (this.image) {
                var a  = this.image.width;
                var b = this.image.height;
                // var radious = Math.sqrt(a * a + b * b).toFixed(0) / 2;
                var radious = (a > b ? a : b) / 2;
                r.w = 2 * (radious + extra);
                r.h = 2 * (radious + extra);
            } else {
                r.w = r.h = 18;
            }
            r.update();
        },
        draw: function(context) {
            context.save();
            context.globalAlpha = this.style[cu.Style.ALPHA];
            this.drawOutline(context);
            this.drawImage(context);
            context.restore();
        },
        drawOutline: function(context) {
            var r = this.rect;
            var l = r.w > r.h ? r.w : r.h;
            var margin = this.style[cu.Style.MARGIN];
            var olw = this.style[cu.Style.OUTLINE_WIDTH];
            var ilw = this.style[cu.Style.INLINE_WIDTH];
            var radOutline = l / 2 - margin - olw / 2;
            var radInline = radOutline - (olw + ilw) / 2;
            var radPadding = radInline - ilw / 2;

            cu.drawCircle(context, r.cx, r.cy, radOutline);

            if (olw > 0) {
                context.strokeStyle = this.style[cu.Style.OUTLINE_COLOR];
                context.lineWidth = olw;
                context.stroke();
            }

            if (ilw > 0) {
                cu.drawCircle(context, r.cx, r.cy, radInline);
                context.strokeStyle = this.style[cu.Style.INLINE_COLOR];
                context.lineWidth = ilw;
                context.stroke();
            }

            cu.drawCircle(context, r.cx, r.cy, radPadding);
            context.fillStyle = this.style[cu.Style.BACKGROUND_COLOR];
            context.fill();

        },
        drawImage: function(context) {
            var r = this.rect;
            if (this.image) {
                var x = r.cx - this.image.width/2;
                var y = r.cy - this.image.height/2;
                cu.drawImage(context, this.image.image, x, y);
            }
        }
    });

    cu.InventoryBus = cu.createClass(cu.Element);
    cu.InventoryBus.include({
        init: function(id, gap, A, holesCount) {
            cu.InventoryBus._super.fn.init.call(this, id);
            var s = cu.Style;
            this.setStyle(s.COLOR, '#D3D3D3');
            this.setStyle(s.LINE_WIDTH, 4);

            this.isHoverable = false;
            this.isSelectable = false;
            this.isDraggable = false;

            var PI = Math.PI;
            this.holesCount = holesCount;
            this.A = null;
            var a = null;
            this.offsetY = null;
            this.updateA(A);
            this.gap = gap;
            this.isStraight = null;
        },
        updateA: function(A) {
            this.A = A;
            var a =  Math.sin(Math.PI/4)*A;
            this.offsetY = [0, a, A, a, 0, -a, -A, -a];
            this.isStraight = (this.holesCount < 5 || A < 30) ? true : false
        },
        canHover: function() {
            return false;
        },
        setStartLocation: function(x, cy) {
            this.setLocation(x, cy - this.A);
        },
        doCalcRect: function() {
            var r = this.rect;
            var lineWidth = this.style[cu.Style.LINE_WIDTH];
            var termRadious = lineWidth * 1.3;
            r.w = (this.holesCount < 1 ? 1 : this.holesCount) * this.gap + 2 * termRadious;
            r.h = this.A > 0 ? 2 * this.A : 2 * lineWidth;
            r.update();
        },
        draw: function(context) {
            var r = this.rect;
            var PI = Math.PI;
            var gap = this.gap;
            var a = PI/4/gap;
            var A = this.A;
            var lineWidth = this.style[cu.Style.LINE_WIDTH];
            var dashLen = 2 * lineWidth;
            var termRadious = lineWidth * 1.3;
            var color = this.style[cu.Style.COLOR];
            var isStraight = this.isStraight;

            context.beginPath();
            var x = r.x + termRadious;
            var y = isStraight ? r.cy : r.cy - A * Math.sin(a * (x - gap - r.x));
            context.moveTo(x, y);

            var startX = x;
            var startY = y;

            var tmpX = x;
            var tmpY = y;
            var isDash = true;
            var l = r.r-termRadious;
            for (;++x<l;) {
                if (isStraight) {
                    var len = x - tmpX;
                } else {
                    y = r.cy - A * Math.sin(a * (x - gap - r.x));
                    var len = Math.sqrt((x - tmpX) * (x - tmpX) + (y - tmpY) * (y - tmpY));
                }
                if (len > dashLen) {
                    tmpX = x;
                    tmpY = y;
                    isDash = isDash ? false : true;
                }
                if (isDash) {
                    context.lineTo(x, y);
                } else {
                    context.moveTo(x, y);
                }
            }
            var stopX = x - 1;
            var stopY = y;

            context.save();
            context.strokeStyle = color;
            context.lineWidth = lineWidth;
            context.stroke();

            cu.drawCircle(context, startX, startY, termRadious);
            context.fillStyle = color;
            context.fill();
            cu.drawCircle(context, stopX, stopY, termRadious);
            context.fill();
            context.restore();
        },
        positionElm: function(n, cuNode) {// 必须先setStartLocation再添加, n从0计数
            var r = this.rect;
            var pY = this.offsetY;
            var m = n % pY.length;
            var lineWidth = this.style[cu.Style.LINE_WIDTH];
            var termRadious = lineWidth * 1.3;
            var x = this.gap * (n+1) + termRadious;
            var y = this.isStraight ? r.cy : r.cy - pY[m];
            cuNode.setIconCenterLocation(x, y);
            cuNode.invalidate();
        }
    });

    cu.IntegTreeBus = cu.createClass(cu.Element);
    cu.IntegTreeBus.include({
        init: function(id, w, h) {
            cu.IntegTreeBus._super.fn.init.call(this, id);
            var s = cu.Style;
            this.setStyle(s.COLOR, '#656565');
            this.setStyle(s.LINE_WIDTH, 4);
            this.setStyle(s.CORNER_RADIOUS, 5);
            this.width = w;
            this.height = h;

            this.isHoverable = false;
            this.isSelectable = false;
            this.isDraggable = false;
        },
        doCalcRect: function() {
            var r = this.rect;
            r.w = this.width;
            r.h = this.height;
            r.update();
        },
        draw: function(context) {
            context.save();
            var r = this.rect;
            var lineWidth = this.style[cu.Style.LINE_WIDTH];
            var a = lineWidth / 2;
            cu.drawRoundRect(context, r.x+a, r.y+a, r.w-a, r.h-a, this.style[cu.Style.CORNER_RADIOUS]);
            context.strokeStyle = this.style[cu.Style.COLOR];
            context.lineWidth = lineWidth;
            context.stroke();
            context.restore();
        }
    });

    cu.IntegTreeNode = cu.createClass(cu.Node);
    cu.IntegTreeNode.include({
        init: function(id, imageName, name) {
            cu.IntegTreeNode._super.fn.init.apply(this, arguments);
        },
        createLabel: function(text) {
            return new cu.IntegTreeNodeLabel(null, text);
        }
    });

    cu.IntegTreeNodeLabel = cu.createClass(cu.Label);
    cu.IntegTreeNodeLabel.include({
        init: function(id, text) {
            cu.IntegTreeNodeLabel._super.fn.init.call(this, id, text);
            var s = cu.Style;
            this.setStyle(s.LABEL_MAXTEXTWIDTH, 85);
            this.setStyle(s.BACKGROUND_COLOR, '#414246');
            this.setStyle(s.PADDING, 4);
            this.setStyle(s.MARGIN, 0);
            this.setStyle(s.OUTLINE_WIDTH, 0);
            this.setStyle(s.OUTLINE_COLOR, '#FF0000');
            this.setStyle(s.FONT, '12px arial, tahoma, sans-serif, helvetica');
            this.setStyle(s.FONT_COLOR, '#FFFFFF');
            this.setStyle(s.CORNER_RADIOUS, 4);
            this.setStyle(s.ALPHA, 1);

            this.shortText = null;
        },
        doCalcRect: function() {
            var text = this.text;
            var metrics, textWidth;
            var mtw = this.style[cu.Style.LABEL_MAXTEXTWIDTH];
            for (var i=0, len=text.length;;i++) {
                this.shortText = text.substring(0, len-i) + (i>0 ? '...' : '');
                metrics = cu.g.measureText(this.shortText);
                textWidth = metrics.width;
                if (textWidth < mtw) {
                    break;
                }
            }
            var fontSize = 12;
            var padding = this.style[cu.Style.PADDING];
            var olw = this.style[cu.Style.OUTLINE_WIDTH];
            var margin = this.style[cu.Style.MARGIN];
            var extra = padding + olw + margin;
            this.rect.w = metrics.width < 60 ? 60 + extra * 2 : textWidth + extra * 2;
            this.rect.h = 2 * 12 + 2 * extra + padding;
            this.rect.update();
        },
        drawText: function(context) {
            var r = this.rect;
            var padding = this.style[cu.Style.PADDING];
            var olw = this.style[cu.Style.OUTLINE_WIDTH];
            var margin = this.style[cu.Style.MARGIN];
            var color = this.style[cu.Style.FONT_COLOR];
            var mtw = this.style[cu.Style.LABEL_MAXTEXTWIDTH];
            var extra = padding + olw + margin;
            var fontSize = 12;
            var text = this.shortText != null ? this.shortText : this.text;

            if (!this.customData['omit']) {
                var status = this.customData['status'];

                context.fillStyle = color;
                context.textAlign = "center";
                context.textBaseline = "middle";
                context.fillText(text, r.cx, r.y + extra + fontSize/2, mtw);

                var gap = 12;
                var x = r.x + extra;
                var y = r.y + extra + fontSize + padding;
                context.textAlign = "left";
                context.textBaseline = "top";
                if ('NORMAL' === status) {
                    cu.drawImage(context, 'normal_10', x, y);
                    x += gap;
                    context.fillStyle = '#00FF00';
                    context.fillText('正常运行', x, y);
                } else if ('ALARM' === status) {
                    cu.drawImage(context, 'alarm_10', x, y);
                    x += gap;
                    context.fillStyle = '#e0013a';
                    context.fillText('故障', x, y);
                } else if ('STOPCHECKED' === status) {
                    cu.drawImage(context, 'stop_check_10', x, y);
                    x += gap;
                    context.fillStyle = '#8A8A8A';
                    context.fillText('停机检修', x, y);
                }
            } else {
                var textLength = text.length;
                var re = /\d+/g;
                var arr = text.match(re);
                var x = r.x + extra;
                var y = r.cy;
                var pos = 0;

                context.textAlign = "left";
                context.textBaseline = "middle";
                if (arr) {
                    for (var i=-1, len=arr.length; ++i<len;) {
                        var start = text.indexOf(arr[i], pos);
                        var end = start + arr[i].length;
                        var strNotMatch = text.substring(pos, start);
                        var strMatch = text.substring(start, end);
                        context.save();
                        context.fillStyle = '#FFFFFF';
                        context.fillText(strNotMatch, x, y);
                        context.restore();

                        if (/\d+/.test(strMatch)) {
                            x += context.measureText(strNotMatch).width;
                            context.save();
                            context.fillStyle = '#52CCFF';
                            context.fillText(strMatch, x, y);
                            context.restore();
                        }
                        x += context.measureText(strMatch).width;
                        pos = end;
                    }
                }
                if (pos !== textLength) {
                    context.save();
                    context.fillStyle = '#FFFFFF';
                    context.fillText(text.substring(pos, textLength), x, y);
                    context.restore();
                }
            }
        }
    });

    cu.drawCircle = function(context, x, y, r) {
        context.beginPath();
        context.arc(x, y, r, 0, Math.PI*2, false);
        context.closePath();
    };

    cu.drawImage = function(context, img, x, y) {
        if (typeof(img) === 'string') {
            img = _images[img];
            if (img) {
                context.drawImage(img.image, x, y);
            }
        } else {
            context.drawImage(img, x, y);
        }
    };

    cu.drawRoundRect = function(context, x, y, w, h, r) {
        if (w < 0 || h < 0 || r < 0) {
            return;
        }
        var short = w <= h ? w : h;
        r = short >= 2 * r ? r : short/2
        var PI = Math.PI;
        context.beginPath();
        context.moveTo(x+r, y);
        context.lineTo(x+w-r, y);
        context.arc(x+w-r, y+r, r, -PI/2, 0);
        context.lineTo(x+w, y+h-r);
        context.arc(x+w-r, y+h-r, r, 0, PI/2);
        context.lineTo(x+r, y+h);
        context.arc(x+r, y+h-r, r, PI/2, PI);
        context.lineTo(x, y+r);
        context.arc(x+r, y+r, r, PI, PI*3/2);
        context.closePath();
    };
})(window);


tip的css样式

.tp-tooltip {position: absolute;border:1px solid #565656;color:#565656;font-size:12px;padding:2px 5px;z-index: 100000;
    border-radius:3px;
    background:-ms-linear-gradient(top, #FFFFFF, #E4E5F0);
    background:-moz-linear-gradient(top , #FFFFFF, #E4E5F0);
    background:-webkit-linear-gradient(top,#FFFFFF, #E4E5F0);
    background:-o-linear-gradient(top , #FFFFFF, #E4E5F0);
}


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值