/**
 * Simple draggable tool, just for demo or testing.
 * Use jquery.
 */
(function (global) {

    var BORDER_WIDTH = 4;
    var $ = global.jQuery;

    global.draggable = {

        /**
         * @param {HTMLElement} mainEl
         * @param {module:echarts/echarts~EChart} chart
         * @param {Object} [opt] {width: ..., height: ...}
         * @param {number} [opt.width] If not specified, use mainEl current width.
         * @param {number} [opt.height] If not specified, use mainEl current height.
         * @param {boolean} [opt.lockX=false]
         * @param {boolean} [opt.lockY=false]
         * @param {number} [opt.throttle=false]
         * @return {type}  description
         */
        init: function (mainEl, chart, opt) {
            opt = opt || {};

            var chartResize = chart ? $.proxy(chart.resize, chart) : function () {};
            if (opt.throttle) {
                chartResize = throttle(chartResize, opt.throttle, true, false);
            }

            var mainEl = $(mainEl);

            $('.draggable-control').remove();

            var controlEl = $(
                '<div class="draggable-control">DRAG<span class="draggable-label"></span></div>'
            );

            controlEl.css({
                'position': 'absolute',
                'border-radius': '30px',
                'width': '60px',
                'height': '60px',
                'line-height': '60px',
                'text-align': 'center',
                'background': '#333',
                'color': '#fff',
                'cursor': 'pointer',
                'font-size': '18px',
                'box-shadow': '0 0 5px #333',
                '-webkit-user-select': 'none',
                'user-select': 'none'
            });

            var label = controlEl.find('.draggable-label');

            label.css({
                'display': 'block',
                'position': 'absolute',
                'color': '#000',
                'font-size': '12px',
                'text-align': 'center',
                'left': 0,
                'top': '65px',
                'width': '60px',
                'line-height': 1
            });

            mainEl.css({
                'position': 'absolute',
                'left': mainEl[0].offsetLeft + 'px',
                'top': mainEl[0].offsetTop + 'px',
                'width': mainEl[0].offsetWidth + 'px',
                'height': mainEl[0].offsetHeight + 'px',
                'border-style': 'solid',
                'border-color': '#ddd',
                'border-width': BORDER_WIDTH + 'px',
                'padding': 0,
                'margin': 0
            });

            mainEl.parent().append(controlEl);

            var controlSize = controlEl[0].offsetWidth;

            var boxSizing = mainEl.css('box-sizing');

            var borderBoxBroder = boxSizing === 'border-box' ? 2 * BORDER_WIDTH : 0;
            var mainContentWidth = opt.width || (mainEl.width() + borderBoxBroder);
            var mainContentHeight = opt.height || (mainEl.height() + borderBoxBroder);

            var mainOffset = mainEl.offset();
            resize(
                mainOffset.left + mainContentWidth + BORDER_WIDTH,
                mainOffset.top + mainContentHeight + BORDER_WIDTH,
                true
            );

            var dragging = false;

            controlEl.on('mousedown', function () {
                dragging = true;
            });

            $(document).on('mousemove', function (e) {
                if (dragging) {
                    resize(e.pageX, e.pageY);
                }
            });

            $(document).on('mouseup', function () {
                dragging = false;
            });



            function resize(x, y, isInit) {
                var mainOffset = mainEl.offset();
                var mainPosition = mainEl.position();
                var mainContentWidth = x - mainOffset.left - BORDER_WIDTH;
                var mainContentHeight = y - mainOffset.top - BORDER_WIDTH;

                if (isInit || !opt.lockX) {
                    controlEl.css(
                        'left',
                        (mainPosition.left + mainContentWidth + BORDER_WIDTH - controlSize / 2) + 'px'
                    );
                    mainEl.css(
                        'width',
                        (mainContentWidth + borderBoxBroder) + 'px'
                    );
                }

                if (isInit || !opt.lockY) {
                    controlEl.css(
                        'top',
                        (mainPosition.top + mainContentHeight + BORDER_WIDTH - controlSize / 2) + 'px'
                    );
                    mainEl.css(
                        'height',
                        (mainContentHeight + borderBoxBroder) + 'px'
                    );
                }

                label.text(Math.round(mainContentWidth) + ' x ' + Math.round(mainContentHeight));

                chartResize();
            }
        }
    };

    function throttle(fn, delay, trailing, debounce) {

        var currCall = (new Date()).getTime();
        var lastCall = 0;
        var lastExec = 0;
        var timer = null;
        var diff;
        var scope;
        var args;
        var isSingle = typeof fn === 'function';
        delay = delay || 0;

        if (isSingle) {
            return createCallback();
        }
        else {
            var ret = [];
            for (var i = 0; i < fn.length; i++) {
                ret[i] = createCallback(i);
            }
            return ret;
        }

        function createCallback(index) {

            function exec() {
                lastExec = (new Date()).getTime();
                timer = null;
                (isSingle ? fn : fn[index]).apply(scope, args || []);
            }

            var cb = function () {
                currCall = (new Date()).getTime();
                scope = this;
                args = arguments;
                diff = currCall - (debounce ? lastCall : lastExec) - delay;

                clearTimeout(timer);

                if (debounce) {
                    if (trailing) {
                        timer = setTimeout(exec, delay);
                    }
                    else if (diff >= 0) {
                        exec();
                    }
                }
                else {
                    if (diff >= 0) {
                        exec();
                    }
                    else if (trailing) {
                        timer = setTimeout(exec, -diff);
                    }
                }

                lastCall = currCall;
            };

            /**
             * Clear throttle.
             * @public
             */
            cb.clear = function () {
                if (timer) {
                    clearTimeout(timer);
                    timer = null;
                }
            };

            return cb;
        }
    }

})(window);
@@ -1023,54 +1023,311 @@
 l);%0A
-%0A            $('.draggable-control').remove();
+            var id = mainEl.attr('data-draggable-id');%0A%0A            if (id == null) %7B%0A                id = +Math.random();%0A                mainEl.attr('data-draggable-id', id);%0A            %7D%0A            else %7B%0A                $('.draggable-control%5Bdata-draggable-id=' + id + '%5D').remove();%0A            %7D
 %0A%0A  

tweak test lib