window.Rubix = window.Rubix || {};

/*
 * debouncedresize: special jQuery event that happens once after a window resize
 *
 * latest version and complete README available on Github:
 * https://github.com/louisremi/jquery-smartresize
 *
 * Copyright 2012 @louis_remi
 * Licensed under the MIT license.
 *
 * This saved you an hour of work?
 * Send me music http://www.amazon.co.uk/wishlist/HNTU0468LQON
 */
(function($) {

var $event = $.event,
    $special,
    resizeTimeout;

$special = $event.special.debouncedresize = {
    setup: function() {
        $( this ).on( "resize", $special.handler );
    },
    teardown: function() {
        $( this ).off( "resize", $special.handler );
    },
    handler: function( event, execAsap ) {
        // Save the context
        var context = this,
            args = arguments,
            dispatch = function() {
                // set correct event type
                event.type = "debouncedresize";
                $event.dispatch.apply( context, args );
            };

        if ( resizeTimeout ) {
            clearTimeout( resizeTimeout );
        }

        execAsap ?
            dispatch() :
            resizeTimeout = setTimeout( dispatch, $special.threshold );
    },
    threshold: 150
};

})(jQuery);

/*
 * throttledresize: special jQuery event that happens at a reduced rate compared to "resize"
 *
 * latest version and complete README available on Github:
 * https://github.com/louisremi/jquery-smartresize
 *
 * Copyright 2012 @louis_remi
 * Licensed under the MIT license.
 *
 * This saved you an hour of work?
 * Send me music http://www.amazon.co.uk/wishlist/HNTU0468LQON
 */
(function($) {

var $event = $.event,
    $special,
    dummy = {_:0},
    frame = 0,
    wasResized, animRunning;

$special = $event.special.throttledresize = {
    setup: function() {
        $( this ).on( "resize", $special.handler );
    },
    teardown: function() {
        $( this ).off( "resize", $special.handler );
    },
    handler: function( event, execAsap ) {
        // Save the context
        var context = this,
            args = arguments;

        wasResized = true;

        if ( !animRunning ) {
            setInterval(function(){
                frame++;

                if ( frame > $special.threshold && wasResized || execAsap ) {
                    // set correct event type
                    event.type = "throttledresize";
                    $event.dispatch.apply( context, args );
                    wasResized = false;
                    frame = 0;
                }
                if ( frame > 9 ) {
                    $(dummy).stop();
                    animRunning = false;
                    frame = 0;
                }
            }, 30);
            animRunning = true;
        }
    },
    threshold: 0
};

})(jQuery);

$.fn.serializeObject=function(){"use strict";var a={},b=function(b,c){var d=a[c.name];"undefined"!=typeof d&&d!==null?$.isArray(d)?d.push(c.value):a[c.name]=[d,c.value]:a[c.name]=c.value};return $.each(this.serializeArray(),b),a};


var RubixListeners = [];

var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
var useTable = isSafari ? 'inline' : 'table';

/**
 * @param {string} id
 * @param {?Object} opts
 * @constructor
 */
Rubix = function(id, opts) {
    this.chart_counter = 0;
    this.master_id = this.uid('master_id');
    RubixListeners.push(this.master_id);

    this.data_stack = {};
    this.chart_stack = {};

    this.area_stack_data_stack = [];
    this.column_stack_data_stack = [];

    this.root_elem = $(id);
    this.root_elem.css('position', 'relative');
    this.root_elem.addClass('rubixcc-main-chart');
    this.root_elem.append('<div class="rubixcc-tooltip"></div>');
    this.root_elem.append('<div class="rubixcc-title"></div>');
    this.root_elem.append('<div class="rubixcc-subtitle"></div>');
    this.root_elem.append('<div class="rubixcc-chart text-center"><div style="margin-top:5px">Loading...</div></div>');
    this.root_elem.append('<div class="rubixcc-legend"></div>');

    var width = opts.width || '100%';
    var height = opts.height || 150;
    this.root_elem.width(width).height(height);

    this.elem = this.root_elem.find('.rubixcc-chart');

    opts.tooltip = opts.tooltip || {};
    this.tooltip = this.root_elem.find('.rubixcc-tooltip');
    this.tooltip.hide();

    if(opts && opts.tooltip && opts.tooltip.animate) {
        this.tooltip.addClass('animate');
    }

    this.tooltip.html("");
    this.tooltip.css({
        'font-family': 'Lato, "Lucida Grande", Arial, Helvetica, sans-serif',
        'font-size': '12px',
        'position': 'absolute',
        'background': 'white',
        'color': '#89949B',
        'padding': '10px 15px',
        'display': 'none',
        'pointer-events': 'none',
        'border-radius': '5px',
        'z-index': 100,
        'min-height': 50,
        'user-select': 'none',
        'cursor': 'default',
        'border': '3px solid ' + (opts.tooltip.color ? opts.tooltip.color : '#89949B'),
        'box-shadow': 'rgba(0, 0, 0, 0.2) 2px 4px 8px',
        'background': 'white'
    });

    opts.legend = opts.legend || {};

    this.legend = this.root_elem.find('.rubixcc-legend');
    this.legend.css({
        'font-family': "Lato, 'Lucida Grande', Arial, Helvetica, sans-serif",
        'text-align': 'center',
        'margin-top': opts.legend.top || '-10px',
        'margin-bottom': opts.legend.bottom || '5px',
        'user-select': 'none',
        'display': opts.hideLegend ? 'none' : 'block'
    });

    this.title = this.root_elem.find('.rubixcc-title');
    this.subtitle = this.root_elem.find('.rubixcc-subtitle');

    this.title.css({
        'font-family': "Lato, 'Lucida Grande', Arial, Helvetica, sans-serif",
        'text-align': 'center',
        'user-select': 'none',
        'font-weight': 'bold',
        'font-size': '16px',
        'color': 'steelblue',
        'margin-top': '10px',
        'cursor': 'default'
    });

    this.subtitle.css({
        'font-family': "Lato, 'Lucida Grande', Arial, Helvetica, sans-serif",
        'text-align': 'center',
        'user-select': 'none',
        'font-size': '12px',
        'color': 'steelblue',
        'margin-top': '5px',
        'cursor': 'default',
        'opacity': 0.8,
        'font-weight': 'bold'
    });

    var self = this;
    this.tooltip.on({
        'mouseover': function(e) {
            d3.select(self.elem.find('.overlay').get(0)).node().__onmousemove(e);
        },
        'mousemove': function(e) {
            d3.select(self.elem.find('.overlay').get(0)).node().__onmousemove(e);
        },
        'mouseout': function(e) {
            $(this).hide();
        }
    });

    this.elem.css({
        'width': '100%',
        'height': parseInt(this.root_elem.get(0).style.height),
        'user-select': 'none',
        'cursor': 'default',
        'position': 'relative'
    });
    this.root_elem.css('height', '100%');
    this.opts = opts || {};

    this.data = {};
    this.charts = {};
    this.extent = [];
    this.extent_coordinates = [];
    this.is_touch_device = 'ontouchstart' in document.documentElement;
    this.d3_eventSource = function () {
        var e = d3.event, s;
        while (s = e.sourceEvent) e = s;
        return e;
    }

    this.custom_interpolations = {
        sankey: function(points) {
            var x0 = points[0][0], y0 = points[0][1], x1, y1, x2,
                path = [x0, ",", y0],
                i = 0,
                n = points.length;
            while (++i < n) {
                x1 = points[i][0], y1 = points[i][1], x2 = (x0 + x1) / 2;
                path.push("C", x2, ",", y0, " ", x2, ",", y1, " ", x1, ",", y1);
                x0 = x1, y0 = y1;
            }
            return path.join("");
        }
    };

    this.xlabelcolor = 'steelblue';
    this.ylabelcolor = 'steelblue';

    this.first_time = true;
    this.last_render = null;

    this.data_changed = false;

    this.setup();
};

Rubix.prototype.setup = function() {
    this._setupOpts();
    this._setupOnce();
    this._setupRedraw();

    this.draw();
};

Rubix.prototype.draw = function() {
    if(!this.data_changed) {
        if(this.last_render !== null)
            if(Date.now() - this.last_render < 300) return;
        this.last_render = Date.now();
    }
    this.data_changed = false;
    if(!this.root_elem.is(':visible')) return;
    this._draw();
};

Rubix.prototype.uid = function(type) {
    return 'rubixcc-' + type +'-' + Math.floor(2147483648*Math.random()).toString(36);
};

/** @private */
Rubix.prototype._setupOpts = function() {
    this.opts.theme_style = this.opts.theme_style || $('body').attr('data-theme') || 'light';
    this.opts.theme_style_color = this.opts.gridColor || ((this.opts.theme_style === 'light') ? '#C0D0E0' : '#555');
    this.opts.theme_focus_line_color = this.opts.focusLineColor || ((this.opts.theme_style === 'light') ? '#C0D0E0' : '#888');
    this.opts.tickColor = this.opts.tickColor || ((this.opts.theme_style === 'light') ? '#666' : '#999');

    this.opts.titleColor = this.opts.titleColor || 'steelblue';
    this.opts.subtitleColor = this.opts.subtitleColor || 'steelblue';

    this.opts.legend_color_brightness = this.opts.legend_color_brightness || 0.5;

    this.title.css('color', this.opts.titleColor);
    this.subtitle.css('color', this.opts.subtitleColor);

    if(this.opts.theme_style === 'dark' || this.opts.tooltip.theme_style === 'dark') {
        this.tooltip.css({
            "color": "#aaa",
            "font-weight": "bold",
            "border": "1px solid #222",
            "background-color": "#303030"
        });
    }

    this.opts.margin = this.opts.margin || {};
    this.margin = {
        top    : (this.opts.margin.top >= 0) ? this.opts.margin.top : 25,
        left   : (this.opts.margin.left >= 0) ? this.opts.margin.left : 25,
        right  : (this.opts.margin.right >= 0) ? this.opts.margin.right : 12.5,
        bottom : (this.opts.margin.bottom >= 0) ? this.opts.margin.bottom : 25
    };

    this.opts.draw = this.opts.draw || {};
    this.opts.draw = {
        grid: (this.opts.draw.grid === false) ? false : true
    };

    this.opts.invertAxes = this.opts.invertAxes || false;
    this.opts.axis = this.opts.axis || {};
    this.opts.axis.x = this.opts.axis.x || {};
    this.opts.axis.y = this.opts.axis.y || {};
    this.axis = {
        x: {
            type: this.opts.axis.x.type || 'linear',
            range: this.opts.axis.x.range || '',
            tickCount : this.opts.axis.x.tickCount,
            tickFormat: this.opts.axis.x.tickFormat || '',
            label: this.opts.axis.x.label || '',
            labelColor: this.opts.axis.x.labelColor || 'steelblue'
        },
        y: {
            type: this.opts.axis.y.type || 'linear',
            range: this.opts.axis.y.range || '',
            tickCount : this.opts.axis.y.tickCount,
            tickFormat: this.opts.axis.y.tickFormat || '',
            label: this.opts.axis.y.label || '',
            labelColor: this.opts.axis.y.labelColor || 'steelblue'
        }
    };

    this.xlabelcolor = this.opts.axis.x.labelColor;
    this.ylabelcolor = this.opts.axis.y.labelColor;

    if(this.axis.x.label.length) {
        if(this.opts.invertAxes) {
            this.margin.left += 15;
        } else {
            this.margin.bottom += 15;
        }
    }

    if(this.axis.y.label.length) {
        if(this.opts.invertAxes) {
            this.margin.bottom += 15;
        } else {
            this.margin.left += 15;
        }
    }

    this.margin.defaultLeft = this.margin.left;

    this.opts.tooltip = this.opts.tooltip || {};
    this.opts.tooltip.format = this.opts.tooltip.format || {};
    this.opts.tooltip.abs = this.opts.tooltip.abs || {};
    this.tooltipFormatter = {
        format: {
            x: this.opts.tooltip.format.x || '',
            y: this.opts.tooltip.format.y || ''
        },
        abs: {
            x: (this.opts.tooltip.abs.hasOwnProperty('x')) ? this.opts.tooltip.abs.x : false,
            y: (this.opts.tooltip.abs.hasOwnProperty('y')) ? this.opts.tooltip.abs.y : false
        }
    }

    if(this.opts.invertAxes) {
        var temp = this.axis.x;
        this.axis.x = this.axis.y;
        this.axis.y = temp;

        var temp2 = this.tooltipFormatter.format.x;
        this.tooltipFormatter.format.x = this.tooltipFormatter.format.y;
        this.tooltipFormatter.format.y = temp2;
    }

    if(this.opts.animationSpeed !== 0) {
        this.animationSpeed = this.opts.animationSpeed || 750;
    }

    this.opts.interval = this.opts.start_interval || 0;

    this.stacked = this.opts.stacked || false;
    this.grouped = this.opts.grouped || false;
    this.offset = this.opts.offset || 'zero';
    this.order  = this.opts.order || 'default';
    this.show_markers = this.opts.show_markers || false;

    this.resize = this.opts.resize || 'debounced';

    this.interpolate = this.opts.interpolate || 'linear';
    switch(this.interpolate) {
        case 'sankey':
            this.interpolate = this.custom_interpolations[this.interpolate];
        break;
        default:
            // do nothing
        break;
    }

    this.master_detail = this.opts.master_detail || false;

    this.master_detail_height = this.opts.master_detail_height || 50;
    if(this.master_detail) {
        this.master_detail_margin_bottom = this.margin.bottom;
        this.margin.bottom = (2 * this.margin.bottom) + this.master_detail_height;
    }

    this.opts.title = this.opts.title || '';
    this.opts.subtitle = this.opts.subtitle || '';

    this.title.html(this.opts.title);
    this.subtitle.html(this.opts.subtitle);
    if(this.opts.title.length || this.opts.subtitle.length) {
        this.elem.css('margin-top', '-20px');
    }
    (this.opts.title.length) ? this.title.show() : this.title.hide();
    (this.opts.subtitle.length) ? this.subtitle.show() : this.subtitle.hide();
};

function d3_scale_ordinal_invert(x) {
  // TODO take into account current ranger

  var _range = this.range().concat();
  var _domain = this.domain().concat();

  _range.sort(function(a, b) {
    if(a > b) {
        return 1;
    } else if(a === b) {
        return 0;
    } else {
        return -1;
    }
  })
  _domain.sort(function(a, b) {
    if(a > b) {
        return 1;
    } else if(a === b) {
        return 0;
    } else {
        return -1;
    }
  })
  return _domain[d3.bisect(_range, x) - 1];
}

/** @private */
Rubix.prototype._setupOnce = function() {
    var self = this;
    this.area_stack_data = [];
    this.area_stack = d3.layout.stack();
    this.area_stack.offset(this.offset);
    this.area_stack.order(this.order);
    this.area_stack.values(function(d) {
        return d.values;
    });
    this.area_stack.x(function(d) {
        return d.x;
    });
    this.area_stack.y(function(d) {
        return d.y;
    });
    this.area_stack.out(function(d, y0, y) {
        d.y0 = y0;
        d.y_new = y;
    });
    this.column_stack_data = [];
    this.column_stack = d3.layout.stack();
    this.column_stack.offset('zero');
    this.column_stack.order('default');
    this.column_stack.values(function(d) {
        return d.values;
    });
    this.column_stack.x(function(d) {
        return d.x;
    });
    this.column_stack.y(function(d) {
        return d.y;
    });
    this.column_stack.out(function(d, y0, y) {
        d.y0 = y0;
        d.y_new = y;
    });
};

/** @private */
Rubix.prototype._setupAxis = function(animate) {
    switch(this.axis.x.type) {
        case 'linear':
            this.x = d3.scale.linear();
            this.x.tickFormat(d3.format(this.axis.x.tickFormat));

            var range = [0, this.width];
            var masterRange = [0, this.width];
            if(this.master_detail) {
                this.x2 = d3.scale.linear();
            }

            if(this.axis.x.range === 'round') {
                this.x.rangeRound(range);

                if(this.master_detail) {
                    this.x2.rangeRound(masterRange);
                }
            } else {
                this.x.range(range);

                if(this.master_detail) {
                    this.x2.range(masterRange);
                }
            }
        break;
        case 'ordinal':
            this.x = d3.scale.ordinal();
            this.x.invert = d3_scale_ordinal_invert.bind(this.x);

            var range = [0, this.width];
            var masterRange = [0, this.width];
            if(this.master_detail) {
                this.canvas.select('.noData').text('Master-Detail chart only for quantitative scales.');
                throw new Error('Master-Detail chart only for quantitative scales.');
            }

            if(this.axis.x.range === 'round') {
                this.x.rangeRoundBands(range);
            } else if(this.axis.x.range == 'column' || this.axis.x.range == 'bar') {
                this.x.rangeRoundBands(range, 0.35);
            } else {
                this.x.rangePoints(range);

                if(this.master_detail) {
                    this.x2.rangePoints(range);
                }
            }
        break;
        case 'datetime':
            this.x = d3.time.scale();
            this.x.ticks(d3.time.year, 50);
            this.x.tickFormat(d3.format(this.axis.x.tickFormat));

            var range = [0, this.width];
            var masterRange = [0, this.width];
            if(this.master_detail) {
                this.x2 = d3.time.scale();
            }

            if(this.axis.x.range === 'round') {
                this.x.rangeRound(range);

                if(this.master_detail) {
                    this.x2.rangeRound(masterRange);
                }
            } else {
                this.x.range(range);

                if(this.master_detail) {
                    this.x2.range(masterRange);
                }
            }
        break;
        default:
            throw new Error('Unknown Scale for X Axis: ' + this.axis.x.type);
        break;
    }

    switch(this.axis.y.type) {
        case 'linear':
            this.y = d3.scale.linear();
            this.y.clamp(true);
            this.y.tickFormat(d3.format(this.axis.y.tickFormat));

            var range = [this.height, 0];
            var masterRange = [this.master_detail_height, 0];
            if(this.master_detail) {
                this.y2 = d3.scale.linear();
                this.y2.clamp(true);
            }

            if(this.axis.y.range === 'round') {
                this.y.rangeRound(range);

                if(this.master_detail) {
                    this.y2.rangeRound(masterRange);
                }
            } else {
                this.y.range(range);

                if(this.master_detail) {
                    this.y2.range(masterRange);
                }
            }
        break;
        case 'ordinal':
            this.y = d3.scale.ordinal();
            this.y.invert = d3_scale_ordinal_invert.bind(this.y);

            var range = [this.height, 0];
            var masterRange = [this.master_detail_height, 0];
            if(this.master_detail) {
                this.y2 = d3.scale.ordinal();
                this.y2.invert = d3_scale_ordinal_invert.bind(this.y2);
            }

            if(this.axis.y.range === 'round') {
                this.y.rangeRoundBands(range);

                if(this.master_detail) {
                    this.y2.rangeRoundBands(masterRange);
                }
            } else if(this.axis.y.range == 'column' || this.axis.y.range == 'bar') {
                this.y.rangeRoundBands(range, 0.35);
            } else {
                this.y.rangePoints(range);

                if(this.master_detail) {
                    this.y2.rangePoints(masterRange);
                }
            }
        break;
        case 'datetime':
            this.y = d3.time.scale();
            this.y.tickFormat(d3.format(this.axis.y.tickFormat));
            this.y.clamp(true);

            var range = [this.height, 0];
            var masterRange = [this.master_detail_height, 0];
            if(this.master_detail) {
                this.y2 = d3.time.scale();
                this.y2.clamp(true);
            }

            if(this.axis.y.range === 'round') {
                this.y.rangeRound(range);

                if(this.master_detail) {
                    this.y2.rangeRound(masterRange);
                }
            } else {
                this.y.range(range);

                if(this.master_detail) {
                    this.y2.range(masterRange);
                }
            }
        break;
        default:
            throw new Error('Unknown Scale for X Axis: ' + this.axis.y.type);
        break;
    }

    this.fontSize = 10;
    var yTicks = Math.floor((this.height / (this.fontSize + 3)) / 2);

    this.xAxis = d3.svg.axis();
    this.xAxis.scale(this.x).orient('bottom');

    if(this.master_detail) {
        this.xAxis2 = d3.svg.axis();
        this.xAxis2.scale(this.x2).orient('bottom');
    }

    if(this.axis.x.range === 'round' && (this.axis.x.type === 'linear' || this.axis.x.type === 'datetime')) {
        if(this.axis.x.type === 'linear') {
            this.xAxis.tickFormat(d3.format(this.axis.x.tickFormat));
        } else if(this.axis.x.type === 'datetime') {
            this.xAxis.tickFormat(d3.time.format(this.axis.x.tickFormat));
        }

        if(this.master_detail) {
            if(this.axis.x.type === 'linear') {
                this.xAxis2.tickFormat(d3.format(this.axis.x.tickFormat));
            } else {
                this.xAxis2.tickFormat(d3.time.format(this.axis.x.tickFormat));
            }
        }
    }

    if(this.axis.x.tickFormat) {
        if(this.axis.x.type === 'linear') {
            this.xAxis.tickFormat(d3.format(this.axis.x.tickFormat));
        } else if(this.axis.x.type === 'datetime') {
            this.xAxis.tickFormat(d3.time.format(this.axis.x.tickFormat));
        }

        if(this.master_detail) {
            if(this.axis.x.type === 'linear') {
                this.xAxis2.tickFormat(d3.format(this.axis.x.tickFormat));
            } else {
                this.xAxis2.tickFormat(d3.time.format(this.axis.x.tickFormat));
            }
        }
    }

    this.xGrid = d3.svg.axis();
    this.xGrid.scale(this.x).orient('bottom').ticks(5).tickSize(-this.height, 0, 0).tickFormat('');

    if(this.axis.x.tickCount !== undefined) {
        this.xGrid.ticks(this.axis.x.tickCount);
    }

    if(this.master_detail) {
        this.xGrid2 = d3.svg.axis();
        this.xGrid2.scale(this.x2).orient('bottom').ticks(5).tickSize(-this.master_detail_height, 0, 0).tickFormat('');
    }

    this.yAxis = d3.svg.axis();
    this.yAxis.scale(this.y).orient('left');

    this.yAxis.ticks(yTicks);

    if(this.axis.y.range === 'round' && (this.axis.y.type === 'linear' || this.axis.y.type === 'datetime')) {
        if(this.axis.y.type === 'linear') {
            this.yAxis.tickFormat(d3.format(this.axis.y.tickFormat));
        } else if(this.axis.y.type === 'datetime') {
            this.yAxis.tickFormat(d3.time.format(this.axis.y.tickFormat));
        }
    }

    if(this.axis.y.tickFormat) {
        if(this.axis.y.type === 'linear') {
            this.yAxis.tickFormat(d3.format(this.axis.y.tickFormat));
        } else if(this.axis.y.type === 'datetime') {
            this.yAxis.tickFormat(d3.time.format(this.axis.y.tickFormat));
        }
    }

    this.yGrid = d3.svg.axis();
    this.yGrid.scale(this.y).orient('left').ticks(5).tickSize(-this.width, 0, 0).tickFormat('');
    if(this.axis.y.tickCount !== undefined) {
        this.yGrid.ticks(this.axis.y.tickCount);
    }
    this.xAxisGroup = this.axis_group.append('g');
    this.xAxisGroup.attr('class', 'x axis');
    this.xAxisGroup.attr('transform', 'translate(0, '+this.height+')');
    this.xAxisGroup.call(this.xAxis);
    this.xAxisGroup.select('path').style('fill', 'none').style('stroke', this.opts.theme_style_color).style('shape-rendering', 'crispEdges');
    this.xAxisGroup.selectAll('line').attr('fill', 'none').attr('stroke', this.opts.theme_style_color).style('shape-rendering', 'crispEdges');

    if(this.master_detail) {
        this.xAxisGroup2 = this.md_root.append('g');
        this.xAxisGroup2.attr('class', 'x axis');
        // this.xAxisGroup2.attr('transform', 'translate(0, '+ this.master_detail_height +')');
        this.xAxisGroup2.call(this.xAxis2);
        this.xAxisGroup2.select('path').style('fill', 'none');
        this.xAxisGroup2.select('line').style('fill', 'none').style('stroke', this.opts.theme_style_color).style('shape-rendering', 'crispEdges');
        this.xAxisGroup2.selectAll('line').attr('fill', 'none').attr('stroke', this.opts.theme_style_color).style('shape-rendering', 'crispEdges');
    }

    this.yAxisGroup = this.axis_group.append('g');
    this.yAxisGroup.attr('class', 'y axis');
    this.yAxisGroup.call(this.yAxis);
    this.yAxisGroup.select('path').style('fill', 'none').style('stroke', this.opts.theme_style_color).style('shape-rendering', 'crispEdges');
    this.yAxisGroup.selectAll('line').attr('fill', 'none').attr('stroke', this.opts.theme_style_color).style('shape-rendering', 'crispEdges');

    if(this.opts.draw.grid) {
        this.xGridGroup = this.grid_group.append('g');
        this.xGridGroup.attr('class', 'x grid');
        this.xGridGroup.attr('transform', 'translate(0,'+this.height+')');
        this.xGridGroup.call(this.xGrid);
        this.xGridGroup.selectAll('path').style('stroke-width', 0);
        this.xGridGroup.selectAll('.tick').style('stroke', this.opts.theme_style_color).style('opacity', 0.7);
    }

    if(this.master_detail) {
        this.xGridGroup2 = this.md_root.select('.md-grid').append('g');
        this.xGridGroup2.attr('class', 'x grid');
        this.xGridGroup2.attr('transform', 'translate(0,'+this.master_detail_height+')');
        this.xGridGroup2.call(this.xGrid2);
        this.xGridGroup2.selectAll('path').style('stroke-width', 0);
        this.xGridGroup2.selectAll('.tick').style('stroke', 'lightgray').style('opacity', 0.7);
    }

    if(this.opts.draw.grid) {
        this.yGridGroup = this.grid_group.append('g');
        this.yGridGroup.attr('class', 'y grid');
        this.yGridGroup.call(this.yGrid);
        this.yGridGroup.selectAll('path').style('stroke-width', 0);
        this.yGridGroup.selectAll('.tick').style('stroke', this.opts.theme_style_color).style('opacity', 0.7);
    }

    this.resetAxis(animate);

    if(this.master_detail) {
        if(this.axis.x.type === 'ordinal') {
            this.canvas.select('.noData').text('Master-Detail chart only for quantitative scales.');
            throw new Error('Master-Detail chart only for quantitative scales.');
        }
        this.brush = d3.svg.brush();
        this.brush.x(this.x2);
        var self = this;
        if(this.axis.x.type === 'datetime') {
            this.brush.on('brushend', function() {
                if(self.is_touch_device) {
                    var coordinates = d3.touches(this, self.d3_eventSource().changedTouches)[0];
                } else {
                    var coordinates = d3.mouse(this);
                }
                var x = coordinates[0];

                if(self.brush.empty()) {
                    var current_x0 = self.x2(+new Date(self.extent[0]));
                    var current_x1 = self.x2(+new Date(self.extent[1]));

                    var distance_x0_x = current_x0 - x;

                    var final_extent = [];
                    if(distance_x0_x < 0) {
                        var start = current_x0 + Math.abs(distance_x0_x);
                        var finish = current_x1 + Math.abs(distance_x0_x);
                        var max_extent = self.x2(+new Date(self.x2.domain()[1]));
                        if(finish > max_extent) {
                            start -= (finish - max_extent);
                            finish = max_extent;
                            final_extent = [
                                self.x2.invert(start),
                                self.x2.invert(finish)
                            ];
                        } else {
                            var diff = Math.abs(start - finish);
                            final_extent = [
                                self.x2.invert(start - diff/2),
                                self.x2.invert(finish - diff/2)
                            ];
                        }
                    } else {
                        var start = current_x0 - Math.abs(distance_x0_x);
                        var finish = current_x1 - Math.abs(distance_x0_x);

                        var diff = Math.abs(start - finish);
                        var final_start = start - diff/2;
                        var final_finish = finish - diff/2;

                        var min_extent = self.x2(+new Date(self.x2.domain()[0]));

                        if(final_start < min_extent) {
                            final_finish += (min_extent - final_start);
                            final_start = min_extent;
                        }
                        final_extent = [
                            self.x2.invert(final_start),
                            self.x2.invert(final_finish)
                        ];
                    }

                    self.extent = final_extent;
                    self.brush.extent(self.extent);
                    self.brush_path.call(self.brush);
                    self._brush();
                }


                var brush_x_pos = self.brush_path.select('.extent').attr('x');
                var brush_width = self.brush_path.select('.extent').attr('width');
                var brush_height = self.brush_path.select('.extent').attr('height');

                self.brush_path.select('.left-extent').attr('height', brush_height).attr('width', brush_x_pos).attr('x', 0);
                var brush_r_x_pos = parseFloat(brush_x_pos)+parseFloat(brush_width);
                self.brush_path.select('.right-extent').attr('height', brush_height).attr('x', brush_r_x_pos).attr('width', self.md_root.attr('width') - brush_r_x_pos);
                self.brush_path.select('.left-border').attr('width', 1).attr('x', brush_x_pos);
                self.brush_path.select('.left-top-border').attr('width', brush_x_pos);
                self.brush_path.select('.right-border').attr('width', 1).attr('x', brush_r_x_pos);
                self.brush_path.select('.right-top-border').attr('x', brush_r_x_pos).attr('width', self.md_root.attr('width') - brush_r_x_pos);
                self.brush_path.select('.bottom-border').attr('x', brush_x_pos).attr('width', brush_width).attr('transform', 'translate(0, ' + (self.master_detail_height+5) + ')');
            });
        } else {
            this.brush.on('brushend', function() {
                if(self.is_touch_device) {
                    var coordinates = d3.touches(this, self.d3_eventSource().changedTouches)[0];
                } else {
                    var coordinates = d3.mouse(this);
                }
                var x = self.x2.invert(coordinates[0]);

                if(self.brush.empty()) {
                    var current_x0 = self.extent[0];
                    var current_x1 = self.extent[1];

                    var distance_x0_x = current_x0 - x;

                    var final_extent = [];
                    if(distance_x0_x < 0) {
                        var start = current_x0 + Math.abs(distance_x0_x);
                        var finish = current_x1 + Math.abs(distance_x0_x);
                        var max_extent = self.x2.domain()[1];
                        if(finish > max_extent) {
                            start -= (finish - max_extent);
                            finish = max_extent;
                            final_extent = [
                                start,
                                finish
                            ];
                        } else {
                            var diff = Math.abs(start - finish);
                            final_extent = [
                                start - diff/2,
                                finish - diff/2
                            ];
                        }
                    } else {
                        var start = current_x0 - Math.abs(distance_x0_x);
                        var finish = current_x1 - Math.abs(distance_x0_x);

                        var diff = Math.abs(start - finish);
                        var final_start = start - diff/2;
                        var final_finish = finish - diff/2;

                        var min_extent = self.x2.domain()[0];

                        if(final_start < min_extent) {
                            final_finish += (min_extent - final_start);
                            final_start = min_extent;
                        }
                        final_extent = [
                            final_start,
                            final_finish
                        ];
                    }

                    self.extent = final_extent;
                    self.brush.extent(self.extent);
                    self.brush_path.call(self.brush);
                    self._brush();
                }

                var brush_x_pos = self.brush_path.select('.extent').attr('x');
                var brush_width = self.brush_path.select('.extent').attr('width');
                var brush_height = self.brush_path.select('.extent').attr('height');

                self.brush_path.select('.left-extent').attr('height', brush_height).attr('width', brush_x_pos).attr('x', 0);
                var brush_r_x_pos = parseFloat(brush_x_pos)+parseFloat(brush_width);
                self.brush_path.select('.right-extent').attr('height', brush_height).attr('x', brush_r_x_pos).attr('width', self.md_root.attr('width') - brush_r_x_pos);
                self.brush_path.select('.left-border').attr('width', 1).attr('x', brush_x_pos);
                self.brush_path.select('.left-top-border').attr('width', brush_x_pos);
                self.brush_path.select('.right-border').attr('width', 1).attr('x', brush_r_x_pos);
                self.brush_path.select('.right-top-border').attr('x', brush_r_x_pos).attr('width', self.md_root.attr('width') - brush_r_x_pos);
                self.brush_path.select('.bottom-border').attr('x', brush_x_pos).attr('width', brush_width).attr('transform', 'translate(0, ' + (self.master_detail_height+5) + ')');
            });
        }
        this.brush.on('brush', function() {
            var type = self.d3_eventSource().type;
            if(type === 'mousemove' || type === 'touchmove') {
                $(window).trigger('rubix.sidebar.off');
                self._brush(true, 'root');
            }
            var brush_x_pos = self.brush_path.select('.extent').attr('x');
            var brush_width = self.brush_path.select('.extent').attr('width');
            var brush_height = self.brush_path.select('.extent').attr('height');

            self.brush_path.select('.left-extent').attr('height', brush_height).attr('width', brush_x_pos).attr('x', 0);
            var brush_r_x_pos = parseFloat(brush_x_pos)+parseFloat(brush_width);
            self.brush_path.select('.right-extent').attr('height', brush_height).attr('x', brush_r_x_pos).attr('width', self.md_root.attr('width') - brush_r_x_pos);
            self.brush_path.select('.left-border').attr('width', 1).attr('x', brush_x_pos);
            self.brush_path.select('.left-top-border').attr('width', brush_x_pos);
            self.brush_path.select('.right-border').attr('width', 1).attr('x', brush_r_x_pos);
            self.brush_path.select('.right-top-border').attr('x', brush_r_x_pos).attr('width', self.md_root.attr('width') - brush_r_x_pos);
            self.brush_path.select('.bottom-border').attr('x', brush_x_pos).attr('width', brush_width).attr('transform', 'translate(0, ' + (self.master_detail_height+5) + ')');
        });
        if(this.extent.length) {
            this.brush.extent(this.extent);
            if(!this.extent_coordinates.length) {
                if(this.axis.x.type === 'datetime' || this.axis.y.type === 'datetime') {
                    this.extent_coordinates = [
                        this.x2(+new Date(this.extent[0])),
                        this.x2(+new Date(this.extent[1]))
                    ];
                } else {
                    this.extent_coordinates = [
                        this.x2(this.extent[0]),
                        this.x2(this.extent[1])
                    ];
                }
            }
            this._brush();
        }
    }
};

Rubix.prototype._setupSeries = function() {
    this.grid_group = this.root.append('g').attr('class', 'grid_group');
    this.axis_group = this.root.append('g').attr('class', 'axis_group');

    if(this.opts.hideGrid) {
        this.grid_group.style('display', 'none');
    }
    if(this.opts.hideAxisAndGrid) {
        this.grid_group.style('display', 'none');
        this.axis_group.style('display', 'none');
    }

    this.root_cb_series = this.root.append('g').attr('class', 'cb_series');
    this.root_stacked_area_series = this.root.append('g').attr('class', 'stacked_area_series');
    this.root_area_series = this.root.append('g').attr('class', 'area_series');
    this.root_line_series = this.root.append('g').attr('class', 'line_series');

    this.focus_line_group = this.root.append('g').attr('class', 'focus_line_group');

    this.symbols_group = this.root.append('g').attr('class', 'symbols');
    this.focus_group = this.root.append('g').attr('class', 'focus_group');

    if(this.master_detail) {
        this.md_root_stacked_area_series = this.md_layers.append('g').attr('class', 'md_stacked_area_series');
        this.md_root_area_series = this.md_layers.append('g').attr('class', 'md_area_series');
        this.md_root_line_series = this.md_layers.append('g').attr('class', 'md_line_series');
    }
};

Rubix.prototype.resetExtent = function() {
    var curr_extent = this.brush.extent();
    if(curr_extent.length) {
        var start = this.extent_coordinates[0];
        var finish = this.extent_coordinates[1];
        if(this.axis.x.type === 'datetime' || this.axis.y.type === 'datetime') {
            this.extent = [this.x2.invert(+new Date(start)), this.x2.invert(+new Date(finish))];
        } else {
            this.extent = [this.x2.invert(start), this.x2.invert(finish)];
        }
        this.brush.extent(this.extent);
        this._brush_nochange();
    }
};

Rubix.prototype._brush_nochange = function() {
    try {
        this.x.domain(this.extent);
        this.callAxis();
        for(var i in this.charts) {
            this.charts[i].noRedraw(this);
        }
    } catch(e) {
        // do nothing
    }
};

Rubix.prototype._brush = function(record, type) {
    var extent = this.brush.empty() ? this.x2.domain() : this.brush.extent();
    if(this.axis.x.type === 'datetime' || this.axis.y.type === 'datetime') {
        this.extent = extent;
        if(record && type === 'root') {
            this.extent_coordinates = [
                this.x2(+new Date(this.extent[0])),
                this.x2(+new Date(this.extent[1]))
            ];
        }
    } else {
        this.extent = extent;
        if(record && type === 'root') {
            this.extent_coordinates = [
                this.x2(this.extent[0]),
                this.x2(this.extent[1])
            ];
        }
    }
    this._brush_nochange();
};

Rubix.prototype._setupOrdinalAxis = function(forced) {
    var data = {}, o_data = {};
    var _data = [], _data1 = [], extentX, extentY, rdata;
    this.crosshair_data = [], _o_data = [];
    var temp = {}, others = {};
    if(this.axis.x.type === 'ordinal' || this.axis.y.type === 'ordinal') {
        for(var series in this.data) {
            rdata = this.data[series];
            var chart = this.charts[series];
            for(var i=0; i<rdata.length; i++) {
                var x = rdata[i].x;
                var y0 = 0;
                if(this.stacked) {
                    if(rdata[i].hasOwnProperty('y0')) {
                        var y0 = rdata[i].y0;
                    }
                    if(rdata[i].hasOwnProperty('y_new')) {
                        if(this.grouped && chart.type === 'column_series') {
                            var y = rdata[i].y_new;
                        } else {
                            var y = rdata[i].y_new + y0;
                        }
                    } else {
                        var y = rdata[i].y;
                    }
                } else {
                    var y = rdata[i].y;
                }
                _data1.push(y);
                if(!data.hasOwnProperty(x)) {
                    data[x] = [];
                }
                data[x].push(y);
                if(forced) {
                    if(!o_data.hasOwnProperty(y)) {
                        o_data[y] = [];
                    }
                    o_data[y].push(x);
                }

                if(!temp.hasOwnProperty(x)) {
                    temp[x] = {};
                }
                temp[x][series] = y;
                if(!others.hasOwnProperty(x)) {
                    others[x] = {};
                }
                others[x][series] = {
                    y0: y0
                };
            }
        }

        for(var point in data) {
            _data.push(point);
        }
        if(forced) {
            for(var point in o_data) {
                _o_data.push(point);
            }
        }

        if(this.stacked) {
            var _minY = d3.min(_data1, function(d) {
                if(isNaN(d)) {
                    return 0;
                }
                return d;
            });

            _minY = _minY < 0 ? _minY : 0;

            var extentY = [_minY, d3.max(_data1, function(d) {
                if(isNaN(d)) {
                    return 0;
                }
                return d;
            })];
        } else {
            var extentY = d3.extent(_data1);
        }
        var yMin = extentY[0];
        var yMax = extentY[1];

        if(this.opts.invertAxes) {
            if(this.axis.y.tickFormat.length) {
                for(var i=0; i<_data.length; i++) {
                    _data[i] = +_data[i];
                }
                _data.sort(function(a, b) {
                    if(a > b) {
                        return 1;
                    } else if(a === b) {
                        return 0
                    } else {
                        return -1;
                    }
                });
            }

            if(forced) {
                if(this.axis.x.tickFormat.length) {
                    for(var i=0; i<_o_data.length; i++) {
                        _o_data[i] = +_o_data[i];
                    }
                    _o_data.sort(function(a, b) {
                        if(a > b) {
                            return 1;
                        } else if(a === b) {
                            return 0
                        } else {
                            return -1;
                        }
                    });
                }

                this.x.domain(_o_data);
                this.y.domain(_data.reverse());

                if(this.master_detail) {
                    this.x2.domain(_o_data);
                    this.y2.domain(_data);
                }
            } else {
                this.x.domain([yMin, yMax]);
                this.y.domain(_data.reverse());

                if(this.master_detail) {
                    this.x2.domain([yMin, yMax]);
                    this.y2.domain(_data);
                }
            }
        } else {
            if(this.axis.x.tickFormat.length) {
                for(var i=0; i<_data.length; i++) {
                    _data[i] = +_data[i];
                }
                _data.sort(function(a, b) {
                    if(a > b) {
                        return 1;
                    } else if(a === b) {
                        return 0
                    } else {
                        return -1;
                    }
                });
            }

            if(forced) {
                if(this.axis.y.tickFormat.length) {
                    for(var i=0; i<_o_data.length; i++) {
                        _o_data[i] = +_o_data[i];
                    }
                    _o_data.sort(function(a, b) {
                        if(a > b) {
                            return 1;
                        } else if(a === b) {
                            return 0
                        } else {
                            return -1;
                        }
                    });
                }

                this.x.domain(_data);
                this.y.domain(_o_data);

                if(this.master_detail) {
                    this.x2.domain(_data);
                    this.y2.domain(_o_data);
                }
            } else {
                this.x.domain(_data);
                this.y.domain([yMin, yMax]);

                if(this.master_detail) {
                    this.x2.domain(_data);
                    this.y2.domain([yMin, yMax]);
                }
            }
        }

        for(var x in temp) {
            if(this.opts.invertAxes) {
                var _x = this.y(x);
                this.crosshair_data.push({
                    x: +_x,
                    y: temp[x],
                    others: others[x]
                });
            } else {
                var _x = this.x(x);
                this.crosshair_data.push({
                    x: +_x,
                    y: temp[x],
                    others: others[x]
                });
            }
        }

        this.crosshair_data.sort(function(a, b) {
            if(a.x > b.x) {
                return 1;
            } else if(a.x === b.x) {
                return 0;
            } else {
                return -1;
            }
        });
    }
};

Rubix.prototype._setupLinearAxis = function() {
    var xMin=null, xMax=null, yMin=null, yMax=null;
    var data, extentX, extentY;
    this.crosshair_data = [];
    var temp = {}, others = {};
    for(var series in this.data) {
        data = this.data[series];
        extentX = d3.extent(data, function(d) {return d.x});
        if(this.stacked) {
            var _minY = d3.min(data, function(d) {
                var val = d.y0 + d.y_new;
                if(isNaN(val)) {
                    if(d.y) return d.y;
                    return 0;
                }
                return val;
            });

            _minY = _minY < 0 ? _minY : 0;

            extentY = [_minY, d3.max(data, function(d) {
                var val = d.y0 + d.y_new;
                if(isNaN(val)) {
                    if(d.y) return d.y;
                    return 0;
                }
                return val;
            })];
        } else {
            extentY = d3.extent(data, function(d) {return d.y;});
        }

        if(xMin === null) {
            xMin = extentX[0];
            xMax = extentX[1];
        } else {
            if(xMin >= extentX[0]) xMin = extentX[0];
            if(xMax <= extentX[1]) xMax = extentX[1];
        }
        if(yMin === null) {
            yMin = extentY[0];
            yMax = extentY[1];
        } else {
            if(yMin >= extentY[0]) yMin = extentY[0];
            if(yMax <= extentY[1]) yMax = extentY[1];
        }

        for(var i=0; i<data.length; i++) {
            var x = data[i].x;
            var y0 = 0;
            if(this.stacked) {
                if(data[i].hasOwnProperty('y0')) {
                    var y0 = data[i].y0;
                }
                if(data[i].hasOwnProperty('y_new')) {
                    var y = data[i].y_new + y0;
                } else {
                    var y = data[i].y;
                }
            } else {
                var y = data[i].y;
            }

            if(!temp.hasOwnProperty(x)) {
                temp[x] = {};
            }
            temp[x][series] = y;
            if(!others.hasOwnProperty(x)) {
                others[x] = {};
            }
            others[x][series] = {
                y0: y0
            };
        }
    }

    if(this.opts.invertAxes) {
        this.x.domain([yMin, yMax]);
        this.y.domain([xMax, xMin]);
        if(this.master_detail) {
            this.x2.domain([yMin, yMax]);
            this.y2.domain([xMax, xMin]);
        }
    } else {
        this.x.domain([xMin, xMax]);
        this.y.domain([yMin, yMax]);
        if(this.master_detail) {
            this.x2.domain([xMin, xMax]);
            this.y2.domain([yMin, yMax]);
        }
    }

    for(var x in temp) {
        this.crosshair_data.push({
            x: +x,
            y: temp[x],
            others: others[x]
        });
    }

    this.crosshair_data.sort(function(a, b) {
        if(a.x > b.x) {
            return 1;
        } else if(a.x === b.x) {
            return 0;
        } else {
            return -1;
        }
    });
};

Rubix.prototype.resetAxis = function(animate) {
    try {
        if(this.axis.x.type === 'linear' && this.axis.y.type === 'linear') {
            this._setupLinearAxis();
        } else if(
            (this.axis.x.type === 'datetime' && this.axis.y.type === 'linear')
        ||  (this.axis.x.type === 'linear' && this.axis.y.type === 'datetime')
        ) {
            this._setupLinearAxis();
        } else if(this.axis.x.type === 'ordinal' && this.axis.y.type === 'ordinal') {
            this._setupOrdinalAxis(true);
        } else if(this.axis.x.type === 'ordinal' || this.axis.y.type === 'ordinal') {
            this._setupOrdinalAxis();
        }

        this.callAxis(animate);
    } catch(e) {
        // do nothing
    }
};

Rubix.prototype.callAxis = function(animate) {
    if(animate) {
        var t = this.root.transition().duration(this.animationSpeed);
        if(this.master_detail) {
            var z = this.md_root.transition().duration(this.animationSpeed);
        }
    } else {
        var t = this.root;
        if(this.master_detail) {
            var z = this.md_root;
        }
    }

    if(this.hasData) {
        if(this.axis.x.type === 'ordinal') {
            var domain = this.x.domain();
            if((this.axis.x.tickCount !== undefined) && (domain.length > this.axis.x.tickCount)) {
                var self = this;
                var feed = d3.range(domain.length).map(function(d) {
                   if(d % self.axis.x.tickCount === 0)
                      return domain[d];
                   return null;
                });

                var cleanedFeed = [];
                for(var i=0; i<feed.length; i++) {
                    if(feed[i] === null) continue;
                    cleanedFeed.push(feed[i]);
                }
                this.xAxis.tickValues(cleanedFeed);
            }
        } else {
            if(this.axis.x.tickCount !== undefined) {
                this.xAxis.ticks(this.axis.x.tickCount);

                if(this.master_detail) {
                    this.xAxis2.ticks(this.axis.x.tickCount);
                }
            }
        }

        if(this.axis.y.type === 'ordinal' || this.axis.x.type === 'linear' || this.axis.x.type === 'datetime') {
            var domain = this.y.domain();
            if((this.axis.y.tickCount !== undefined) && (domain.length > this.axis.y.tickCount)) {
                var self = this;
                var feed = d3.range(domain.length).map(function(d) {
                   if(d % self.axis.y.tickCount === 0) {
                      return domain[d];
                   }
                   return '';
                });

                this.yAxis.tickValues(feed);
            }
        } else {
            if(this.axis.y.tickCount !== undefined) {
                this.yAxis.ticks(this.axis.y.tickCount);
            }
        }

        this.canvas.select('.noData').style('display', 'none');
        try {
            t.selectAll('.x.axis').style('display', null).call(this.xAxis);
            t.selectAll('.y.axis').style('display', null).call(this.yAxis);
            t.selectAll('.x.grid').style('display', null).call(this.xGrid);
            t.selectAll('.y.grid').style('display', null).call(this.yGrid);
        } catch(e) {
            // do nothing
        }
        this.xAxisGroup.selectAll('line').attr('fill', 'none').attr('stroke', this.opts.theme_style_color).style('shape-rendering', 'crispEdges');
        this.yAxisGroup.selectAll('line').attr('fill', 'none').attr('stroke', this.opts.theme_style_color).style('shape-rendering', 'crispEdges');

        if(this.opts.draw.grid) {
            this.xGridGroup.selectAll('path').style('stroke-width', 0);
            this.xGridGroup.selectAll('.tick').style('stroke', this.opts.theme_style_color).style('opacity', 0.7);
            this.yGridGroup.selectAll('path').style('stroke-width', 0);
            this.yGridGroup.selectAll('.tick').style('stroke', this.opts.theme_style_color).style('opacity', 0.7);
        }

        this.xAxisGroup.selectAll('text').style('font', (this.fontSize+1)+'px Lato, "Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif').style('direction', 'ltr').style('fill', this.opts.tickColor).style('color', this.opts.tickColor).style('font-style', '').style('font-variant', '').style('font-weight', '').style('line-height', '');
        this.yAxisGroup.selectAll('text').style('font', (this.fontSize+1)+'px Lato, "Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif').style('direction', 'ltr').style('fill', this.opts.tickColor).style('color', this.opts.tickColor).style('font-style', '').style('font-variant', '').style('font-weight', '').style('line-height', '');
        if(this.opts.theme_style === 'dark') {
            this.xAxisGroup.selectAll('text').style('font-weight', 'bold');
            this.yAxisGroup.selectAll('text').style('font-weight', 'bold');
        }
    } else {
        this.canvas.select('.noData').style('display', null);
        t.selectAll('.x.axis').style('display', 'none');
        t.selectAll('.y.axis').style('display', 'none');
        t.selectAll('.x.grid').style('display', 'none');
        t.selectAll('.y.grid').style('display', 'none');
    }
    if(this.master_detail) {
        if(this.hasData) {
            this.canvas.select('.noData').style('display', 'none');
            z.selectAll('.x.axis').style('display', null).call(this.xAxis2);
            z.selectAll('.x.grid').style('display', null).call(this.xGrid2);
            if(this.xAxisGroup2) {
                this.xAxisGroup2.selectAll('line').attr('fill', 'none').attr('stroke', this.opts.theme_style_color).style('shape-rendering', 'crispEdges');
                this.xGridGroup2.selectAll('path').style('stroke-width', 0);
                this.xGridGroup2.selectAll('.tick').style('stroke', this.opts.theme_style_color).style('opacity', 0.7);
                this.xAxisGroup2.selectAll('text').style('font', (this.fontSize+1)+'px Lato, "Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif').style('direction', 'ltr').style('fill', this.opts.tickColor).style('color', this.opts.tickColor).style('font-style', '').style('font-variant', '').style('font-weight', '').style('line-height', '');
                if(this.opts.theme_style === 'dark') {
                    this.xAxisGroup2.selectAll('text').style('font-weight', 'bold');
                }
            }
            if(this.yAxisGroup2) {
                this.yAxisGroup2.selectAll('text').style('font', (this.fontSize+1)+'px Lato, "Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif').style('direction', 'ltr').style('fill', this.opts.tickColor).style('color', this.opts.tickColor).style('font-style', '').style('font-variant', '').style('font-weight', '').style('line-height', '');
                if(this.opts.theme_style === 'dark') {
                    this.yAxisGroup.selectAll('text').style('font-weight', 'bold');
                }
            }
        } else {
            this.canvas.select('.noData').style('display', null);
            z.selectAll('.x.axis').style('display', 'none');
            z.selectAll('.y.axis').style('display', 'none');
            z.selectAll('.x.grid').style('display', 'none');
            z.selectAll('.y.grid').style('display', 'none');
        }
    }


    if(this.opts.hideYAxis) {
        this.yAxisGroup.style('display', 'none');
    }
    if(this.opts.hideXAxis) {
        this.xAxisGroup.style('display', 'none');
    }
    if(this.opts.hideXAxisTickLines) {
        this.xGridGroup.style('display', 'none');
    }
    if(this.opts.hideYAxisTickLines) {
        this.yGridGroup.style('display', 'none');
    }
};

/** @private */
Rubix.prototype._setupRedraw = function() {
    var self = this;

    $(window).on('orientationchange.'+this.master_id, function() {
        self.draw();
    });

    $(window).on('rubix.redraw.'+this.master_id, function() {
        self.draw();
    });

    switch(this.resize) {
        case 'always':
            $(window).on('resize.rubix.'+this.master_id, function() {
                self.draw();
            });
        break;
        case 'debounced':
            $(window).on('debouncedresize.rubix.'+this.master_id, function() {
                self.draw();
            });
        break;
        case 'throttled':
            $(window).on('throttledresize.rubix.'+this.master_id, function() {
                self.draw();
            });
        break;
        default:
            throw new Error('Unknown resize type!');
        break;
    }
};

/**
 * @private
 */
Rubix.prototype._draw = function() {
    this._cleanElement();
    this._setSize();
    this._setupCanvas();
    this._setupRoot();
    this._setupSeries();
    this._setupAxis();

    for(var i in this.charts) {
        this.charts[i].redraw(this);
    }
};

/** @private */
Rubix.prototype._cleanElement = function() {
    this.elem.children().remove();
};

/** @private */
Rubix.prototype._setSize = function() {
    this.outerWidth  = this.elem.width();
    this.outerHeight = this.elem.height();

    if(this.opts.hideYAxis) {
        this.margin.left = 0;
        this.margin.right = 0;
    }

    if(this.opts.hideAxisAndGrid) {
        this.margin.left = 0;
        this.margin.right = 0;
    }

    this.width  = this.outerWidth - this.margin.left - this.margin.right;
    this.height = this.outerHeight - this.margin.top - this.margin.bottom;
};

/** @private */
Rubix.prototype._setupCanvas = function() {
    this.elem.html('');
    this.canvas = d3.select(this.elem.get(0)).append('svg');
    this.canvas.attr('width', this.outerWidth);
    this.canvas.attr('height', this.outerHeight);

    this.canvas.append('desc').text("Powered by Rubix");

    this.defs = this.canvas.append('defs');

    this.defs.append('clipPath')
           .attr('id', this.master_id+'-clip')
         .append('rect')
           .attr('width', this.width)
           .attr('height', this.height+20)
           .attr('transform', 'translate(0, -20)');
};

/** @private */
Rubix.prototype._setupRoot = function() {
    this.root = this.canvas.append('g');
    this.root.attr('width', this.width);
    this.root.attr('height', this.height);
    if(!this.opts.hideAxisAndGrid) {
        this.root.attr('transform', 'translate('+this.margin.left+','+this.margin.top+')');
    } else {
        this.root.attr('transform', 'translate(0,'+this.margin.top+')');
    }

    if(!this.hasData) {
        this.canvas.append('text').attr('class', 'noData').attr('font-size', '30px').attr('text-anchor', 'middle').attr('transform', 'translate('+this.outerWidth/2+','+this.outerHeight/2+')').attr('font-family', 'Lato, "Lucida Grande", Arial, Helvetica, sans-serif').text("No Data");
    } else {
        this.canvas.select('.noData').style('display', 'none');
    }

    if(this.master_detail) {
        this.md_root = this.canvas.append('g');
        this.md_root.attr('width', this.width);
        this.md_root.attr('height', this.master_detail_height);
        if(!this.opts.hideAxisAndGrid) {
            this.md_root.attr('transform', 'translate('+this.margin.left+','+(this.margin.top+this.height+this.master_detail_margin_bottom)+')');
        }
        var rect = this.md_root.append('rect');
        rect.attr('width', this.md_root.attr('width'));
        rect.attr('height', this.md_root.attr('height'));
        rect.attr('fill', 'none');
        rect.attr('shape-rendering', 'crispEdges');
        this.md_root.append('g').attr('class', 'md-grid').attr('width', this.md_root.attr('width'));
        this.md_layers = this.md_root.append('g').attr('class', 'md-layers').attr('clip-path', 'url(#'+this.master_id+'-clip)');

        this.defs.append('clipPath')
                .attr('id', this.master_id+'-clip-symbols')
            .append('rect')
                .attr('width', this.md_root.attr('width'))
                .attr('height', this.height+20)
                .attr('transform', 'translate(0, -10)');

        this.defs.append('clipPath')
                .attr('id', this.master_id+'-clip-brush')
            .append('rect')
                .attr('width', this.md_root.attr('width')+20)
                .attr('height', this.master_detail_height)
                .attr('transform', 'translate(-10, 1)');
    }
};

Rubix.prototype.marginLeft = function(_left_) {
    if(!arguments.length) return this.margin.left;

    if(10 + _left_ + 10 < this.margin.defaultLeft) {
        this.margin.left = this.margin.defaultLeft;
    } else {
        this.margin.left = 10 + _left_ + 10;
    }
    if(this.axis.x.label.length) {
        if(this.opts.invertAxes) {
            this.margin.left += 15;
        }
    }
    if(this.axis.y.label.length) {
        if(!this.opts.invertAxes) {
            this.margin.left += 15;
        }
    }
    this.draw();
};

Rubix.prototype.forceRedraw = function() {
    try {
        this.hasData = false;
        for(var i in this.charts) {
            this.hasData = true;
            this.charts[i].forceRedraw(this);
        }
        if(this.hasData) {
            this.canvas.select('.noData').style('display', 'none');
            this.root.selectAll('.axis').style('display', null);
        } else {
            this.canvas.select('.noData').style('display', null);
            this.root.selectAll('.axis').style('display', 'none');
        }
    } catch(e) {
        // do nothing
    }
};

// flicked from crossfilter
Rubix.prototype.resizePath = function(d) {
    var e = +(d == 'e'),
        x = e ? 1 : -1,
        y = this.master_detail_height/2;
    return 'M' + (0.5 * x) + ',' + y
        + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
        + 'V' + (2 * y - 6)
        + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
        + 'Z'
        + 'M' + (2.5 * x) + ',' + (y + 8)
        + 'V' + (2 * y - 8)
        + 'M' + (4.5 * x) + ',' + (y + 8)
        + 'V' + (2 * y - 8);
};

Rubix.prototype._createBrushPath = function() {
    var self = this;
    if(this.brush_path) this.brush_path.remove();
    this.brush_path = this.md_root.append('g');
    this.brush_path.attr('height', this.master_detail_height+5);
    this.brush_path.attr('width', this.md_root.attr('width'));
    this.brush_path.attr('class', 'x brush');
    this.brush_path.attr('transform', 'translate(0, -4)');
    // this.brush_path.attr('clip-path', 'url(#'+this.master_id+'-clip-brush)');
    this.brush_path.call(this.brush);

    var color = 'rgb(16, 16, 16)';
    if(this.opts.theme_style === 'light') {
        color = 'white';
    }

    this.brush_path.selectAll('.extent').attr('stroke', 'none').attr('fill-opacity', 0).attr('shape-rendering', 'crispEdges');
    this.brush_path.insert('rect', '.resize').attr('class', 'left-extent').attr('fill-opacity', 0.5).attr('fill', color).style('cursor', 'crosshair');
    this.brush_path.insert('rect', '.resize').attr('class', 'right-extent').attr('fill-opacity', 0.5).attr('fill', color).style('cursor', 'crosshair');
    this.brush_path.insert('rect', '.resize').attr('class', 'left-border').attr('x', 0).style('fill', this.opts.theme_style_color);
    this.brush_path.insert('rect', '.resize').attr('class', 'left-top-border').attr('x', 0).style('fill', this.opts.theme_style_color);
    this.brush_path.insert('rect', '.resize').attr('class', 'right-border').style('fill', this.opts.theme_style_color);
    this.brush_path.insert('rect', '.resize').attr('class', 'right-top-border').style('fill', this.opts.theme_style_color);
    this.brush_path.insert('rect', '.resize').attr('class', 'bottom-border').style('fill', 'rgb(16, 16, 16)').attr('fill-opacity', 0.3);

    var brush_x_pos = this.brush_path.select('.extent').attr('x');
    var brush_width = this.brush_path.select('.extent').attr('width');
    var brush_height = this.brush_path.select('.extent').attr('height');

    this.brush_path.select('.left-extent').attr('height', brush_height).attr('width', brush_x_pos).attr('x', 0);
    var brush_r_x_pos = parseFloat(brush_x_pos)+parseFloat(brush_width);
    this.brush_path.select('.right-extent').attr('height', brush_height).attr('x', brush_r_x_pos).attr('width', this.md_root.attr('width') - brush_r_x_pos);

    this.brush_path.select('.left-border').attr('width', 1).attr('x', brush_x_pos);
    this.brush_path.select('.left-top-border').attr('width', brush_x_pos);
    this.brush_path.select('.right-border').attr('width', 1).attr('x', brush_r_x_pos);
    this.brush_path.select('.right-top-border').attr('x', brush_r_x_pos).attr('width', this.md_root.attr('width') - brush_r_x_pos);
    this.brush_path.select('.bottom-border').attr('x', brush_x_pos).attr('width', brush_width).attr('transform', 'translate(0, ' + (this.master_detail_height+5) + ')');

    this.brush_path.selectAll('.resize').append('path').attr('d', function(d) {
        return self.resizePath(d);
    }).style('fill', '#EEE').style('stroke', '#666').attr('transform', 'translate(0, -'+this.master_detail_height/5+')');

    var inner_rect = this.brush_path.selectAll('rect');
    inner_rect.attr('height', this.master_detail_height+5);

    this.brush_path.select('.left-top-border').attr('height', 1);
    this.brush_path.select('.right-top-border').attr('height', 1);
    this.brush_path.select('.bottom-border').attr('height', 1);

    if(!this.extent.length) {
        this.extent = this.x2.domain();
        this.brush.extent(this.extent);
        this._brush();
    }
};

Rubix.prototype.bisectLeft = d3.bisector(function(d) { return d.x; }).left;
Rubix.prototype.bisectRight = d3.bisector(function(d) { return d.x; }).right;

Rubix.prototype.move_tooltip_x = function(dx, ys, points) {
    var dy, mid;
    if(ys.length) {
        dy = d3.mean(ys)/ys.length;
    }

    var copy = dx;
    if(this.axis.x.range === 'column' || this.axis.x.range === 'bar') {
        dx += this.x.rangeBand()/2;
    }

    var _elem = this.root_elem;

    var tooltipPadding = (this.tooltip.outerWidth() - this.tooltip.width())/2;

    var left = dx + this.margin.left + tooltipPadding;
    var top  = dy;

    var elem_far_right = _elem.width();

    var tooltip_far_right = left + this.tooltip.outerWidth();

    if(this.axis.x.range === 'column' || this.axis.x.range === 'bar') {
        dx = copy - this.x.rangeBand()/2;
    }

    if(tooltip_far_right > elem_far_right) {
        left -= (tooltip_far_right  - dx - this.margin.left + tooltipPadding);
    }

    this.tooltip.css('display', 'inline');
    this.tooltip.css('display', useTable);
    this.tooltip.css({
        'left': left,
        'top' : top
    });

    var html = "", formatterX, formatterY;
    if(this.tooltipFormatter.format.x.length) {
        if(this.axis.x.type === 'datetime') {
            formatterX = d3.time.format(this.tooltipFormatter.format.x);
        } else {
            formatterX = d3.format(this.tooltipFormatter.format.x);
        }
    } else {
        formatterX = function(d) { return d; };
    }
    if(this.tooltipFormatter.format.y.length) {
        if(this.axis.y.type === 'datetime') {
            formatterY = d3.time.format(this.tooltipFormatter.format.y);
        } else {
            formatterY = d3.format(this.tooltipFormatter.format.y);
        }
    } else {
        formatterY = function(d) { return d; };
    }
    for(var name in points) {
        var _x, _y;
        if(this.axis.x.type === 'datetime') {
            _x = formatterX(new Date(points[name].x));
        } else {
            if(points[name].invert) {
                _x = formatterX(this.x.invert(points[name].x));
            } else {
                _x = formatterX(points[name].x);
            }
        }
        if(this.axis.y.type === 'datetime') {
            _y = (points[name].y !== null) ? formatterY(new Date(points[name].y)) : null;
        } else {
            if(points[name].invert) {
                _y = (points[name].y !== null) ? formatterY(this.y.invert(points[name].y)) : null;
            } else {
                _y = formatterY(points[name].y);
            }
        }

        _x = this.tooltipFormatter.abs.x ? Math.abs(_x) : _x;
        _y = this.tooltipFormatter.abs.y ? Math.abs(_y) : _y;
        var series = "<div style='color: "+points[name].opts.color+"; margin-bottom: 2px; line-height: 22px;'><b style='position:relative; top: -5px; left: -2px;'><span style='font-size: 22px;'>■ </span><span style='position:relative; top: -3px; left: -2px;'>"+name+"</span></b></div>";
        var x = "<div style='font-size: 10px; margin-top: -10px;'>x : " + _x + " </div>";
        var y = "<div style='font-size: 10px; margin-top: -5px;'>y : " + _y + " </div><br>";
        html = (series+x+y) + html;
    }

    html = html.slice(0, html.length-4);

    this.tooltip.html(html);

    var tooltipHeight = this.tooltip.outerHeight();
    var total_height = tooltipHeight + dy;
    if(total_height >= _elem.height()) {
        top = this.margin.top;
        this.tooltip.css('top', top);
    }
};


Rubix.prototype.move_tooltip_y = function(dy, yx, points) {
    try {
    var dx, mid;
    if(yx.length) {
        dx = d3.max(yx);
    }

    var copy = dx;
    if(this.axis.x.range === 'column' || this.axis.x.range === 'bar') {
        dx += this.y.rangeBand()/2;
    }

    var _elem = $(this.canvas.node());

    var tooltipPadding = (this.tooltip.outerWidth() - this.tooltip.width())/2;

    var left = dx + this.margin.left + tooltipPadding;
    var top  = dy;

    var elem_far_right = _elem.width();

    var tooltip_far_right = left + this.tooltip.outerWidth();

    if(this.axis.x.range === 'column' || this.axis.x.range === 'bar') {
        dx = copy - this.y.rangeBand()/2;
    }

    if(tooltip_far_right > elem_far_right) {
        left -= tooltip_far_right  - dx - this.margin.left + tooltipPadding;
    }

    var elem_far_bottom = _elem.height();
    var tooltip_far_bottom = top + this.tooltip.outerHeight() + this.margin.bottom;

    if(tooltip_far_bottom > elem_far_bottom) {
        top -= (tooltip_far_bottom - elem_far_bottom);
    }

    this.tooltip.css('display', 'inline');
    this.tooltip.css('display', useTable);
    this.tooltip.css({
        'left': left,
        'top' : top
    });


    var html = "", formatterX, formatterY;
    if(this.tooltipFormatter.format.x.length) {
        if(this.axis.x.type === 'datetime') {
            formatterX = d3.time.format(this.tooltipFormatter.format.x);
        } else {
            formatterX = d3.format(this.tooltipFormatter.format.x);
        }
    } else {
        formatterX = function(d) { return d; };
    }
    if(this.tooltipFormatter.format.y.length) {
        if(this.axis.y.type === 'datetime') {
            formatterY = d3.time.format(this.tooltipFormatter.format.y);
        } else {
            formatterY = d3.format(this.tooltipFormatter.format.y);
        }
    } else {
        formatterY = function(d) { return d; };
    }
    for(var name in points) {
        var _x, _y;
        if(this.axis.y.type === 'datetime') {
            _x = formatterY(new Date(points[name].y));
        } else {
            if(points[name].invert) {
                _x = formatterY(this.y.invert(points[name].y));
            } else {
                _x = formatterY(points[name].y);
            }
        }
        if(this.axis.x.type === 'datetime') {
            _y = formatterX(new Date(points[name].x));
        } else {
            if(points[name].invert) {
                _y = formatterX(this.x.invert(points[name].x));
            } else {
                _y = formatterX(points[name].x);
            }
        }

        _x = this.tooltipFormatter.abs.x ? Math.abs(_x) : _x;
        _y = this.tooltipFormatter.abs.y ? Math.abs(_y) : _y;
        var series = "<div style='color: "+points[name].opts.color+"; margin-bottom: 2px'><b style='position:relative; top: -5px; left: -2px;'><span style='font-size: 22px;'>■ </span><span style='position:relative; top: -3px; left: -2px;'>"+name+"</span></b></div>";
        var x = "<div style='font-size: 10px; margin-top: -10px;'>x : " + _x + " </div>";
        var y = "<div style='font-size: 10px; margin-top: -5px;'>y : " + _y + " </div><br>";
        html = (series+x+y) + html;
    }

    html = html.slice(0, html.length-4);

    this.tooltip.html(html);
    } catch(e) {
        // do nothing
    }
};

Rubix.prototype.overlayX = function(self, coordinates) {
    var ycord = coordinates[1];
    try {
        if(self.axis.x.type === 'ordinal') {
            var x0 = coordinates[0];
            if(self.axis.x.range === 'column' || self.axis.x.range === 'bar') {
                x0 = x0 - self.x.rangeBand()/2;
            }

            var i  = self.bisectLeft(self.crosshair_data, x0, 1);
        } else {
            var x0 = self.x.invert(coordinates[0]+1);
            var i  = self.bisectLeft(self.crosshair_data, x0, 1);
        }

        var d0 = self.crosshair_data[i - 1],
            d1 = self.crosshair_data[i],
            d  = x0 - d0.x > d1.x - x0 ? d1 : d0;

        var y = d.y;
        var others = d.others;
        var xpos;
        var ys = [];
        var points = {};
        var ok = [];
        for(var name in self.charts) {
            try {
                if(y.hasOwnProperty(name)) {
                    if(self.axis.x.type === 'ordinal') {
                        if(y[name] !== null && d.others[name].y0 !== null) {
                            ok.push(true);
                            var dx = d.x;
                            var dy = self.y(y[name]);
                            if(self.axis.x.range === 'column' || self.axis.x.range === 'bar') {
                                if(self.grouped && self.charts[name].hasOwnProperty('count')) {
                                    dx = d.x + ((self.x.rangeBand()/(self.charts[name].layers.length)) * (self.charts[name].count)) + self.x.rangeBand()/(2*self.charts[name].layers.length);
                                } else {
                                    dx += self.x.rangeBand()/2;
                                }
                            }

                            self.charts[name].focus.attr('transform', 'translate(' + dx + ',' + dy +')').style('display', null);

                            if(self.axis.x.range === 'column' || self.axis.x.range === 'bar') {
                                if(self.grouped && self.charts[name].hasOwnProperty('count')) {
                                    xpos = d.x + self.x.rangeBand()/2;
                                } else {
                                    xpos = dx;
                                }
                            } else {
                                xpos = dx;
                            }
                            ys.push(dy);
                            points[name] = {
                                x: dx,
                                y: y[name] ? dy : null,
                                opts: self.charts[name].opts,
                                invert: true
                            };
                        }
                    } else {
                        ok.push(true);
                        var dx = self.x(d.x);
                        var dy = self.y(y[name]);
                        self.charts[name].focus.attr('transform', 'translate(' + dx + ',' + dy +')').style('display', null);

                        xpos = dx;
                        ys.push(dy);
                        points[name] = {
                            x: d.x,
                            y: y[name],
                            opts: self.charts[name].opts,
                            invert: false
                        };
                    }
                    if(ok.length) {
                        self.charts[name].on_focus(dx, dy);
                    }
                } else {
                    self.charts[name].focus.style('display', 'none');
                    self.charts[name].off_focus();
                }
            } catch(e) {
                // do nothing
            }
        }
        if(ok.length) {
            if(self.axis.x.type === 'ordinal') {
                var dx = d.x;
                if(self.axis.x.range === 'column' || self.axis.x.range === 'bar') {
                    dx += self.x.rangeBand()/2;
                }
                self.focusLine.style('display', null).attr('transform', 'translate(' + dx + ','+ '0)');
            } else {
                self.focusLine.style('display', null).attr('transform', 'translate(' + self.x(d.x) + ','+ '0)');
            }

            self.move_tooltip_x(xpos, ys, points);
        }
    } catch(e) {
        // do nothing
    }
};

Rubix.prototype.overlayY = function(self, coordinates) {
    // here .x is .y and .y should be .x due to axis inversion
    var xcord = coordinates[0];
    var y0 = coordinates[1];

    try {
        var len = 0;
        if(self.axis.y.type === 'ordinal') {
            if(self.axis.y.range === 'column' || self.axis.y.range === 'bar') {
                y0 = y0 - self.y.rangeBand()/2;
            }
        } else {
            y0 = self.y.invert(y0);
        }
        var i = self.bisectRight(self.crosshair_data, y0, 1);

        var d0 = self.crosshair_data[i - 1],
            d1 = self.crosshair_data[i],
            d  = y0 - d0.x > d1.x - y0 ? d1 : d0;

        var y = d.y;
        var others = d.others;
        var xpos;
        var ys = [];
        var points = {};
        var ok = [];
        for(var name in self.charts) {
            try {
                if(y.hasOwnProperty(name)) {
                    if(self.axis.y.type === 'ordinal') {
                        if(y[name] !== null && d.others[name].y0 !== null) {
                            ok.push(true);
                            var dx = d.x;
                            var dy = self.x(y[name]);
                            if(self.axis.y.range === 'column' || self.axis.y.range === 'bar') {
                                if(self.grouped && self.charts[name].hasOwnProperty('count')) {
                                    dx = d.x + ((self.y.rangeBand()/(self.charts[name].layers.length)) * (self.charts[name].count)) + self.y.rangeBand()/(2*self.charts[name].layers.length);
                                } else {
                                    dx += self.y.rangeBand()/2;
                                }
                            }

                            self.charts[name].focus.attr('transform', 'translate(' + dy + ',' + dx +')').style('display', null);
                            xpos = dx;
                            ys.push(dy);
                            points[name] = {
                                x: dy,
                                y: dx,
                                opts: self.charts[name].opts,
                                invert: true
                            };
                        }
                    } else {
                        ok.push(true);
                        var dx = self.y(d.x);
                        var dy = self.x(y[name]);
                        self.charts[name].focus.attr('transform', 'translate(' + dy + ',' + dx +')').style('display', null);
                        xpos = dx;
                        ys.push(dy);
                        points[name] = {
                            x: y[name],
                            y: d.x,
                            opts: self.charts[name].opts,
                            invert: false
                        };
                    }
                    if(ok.length) {
                        self.charts[name].on_focus(dy, dx);
                    }
                } else {
                    self.charts[name].focus.style('display', 'none');
                    self.charts[name].off_focus();
                }
            } catch(e) {
                // do nothing
            }
        }
        if(ok.length) {
            if(self.axis.y.type === 'ordinal') {
                var dx = d.x;
                if(self.axis.y.range === 'column' || self.axis.y.range === 'bar') {
                    dx += self.y.rangeBand()/2;
                }
                self.focusLine.style('display', null).attr('transform', 'translate(0,' + dx + ')');
            } else {
                self.focusLine.style('display', null).attr('transform', 'translate(0,'+self.y(d.x)+')');
            }

            self.move_tooltip_y(xpos, ys, points);
        }
    } catch(e) {
        // do nothing
    }
};

Rubix.prototype.resetFocus = function(forced) {
    if(!this.width) return;
    if(!this.height) return;

    if(this.focusLine) this.focusLine.remove();
    if(this.symbols) this.symbols.remove();
    if(this.overlay) this.overlay.remove();

    if(this.xlabel) this.xlabel.remove();
    if(this.ylabel) this.ylabel.remove();
    var self = this;

    if(this.axis.x.label.length) {
        this.xlabel = this.root.append('text');
        this.xlabel.style('font-size', '12px');
        this.xlabel.style('font-weight', 'bold');
        this.xlabel.style('font-family', 'Lato, "Lucida Grande", Arial, Helvetica, sans-serif');
        this.xlabel.attr('fill', this.xlabelcolor || 'steelblue');
        this.xlabel.attr('stroke', 'none');
        this.xlabel.style('text-anchor', 'middle');
        if(this.opts.invertAxes) {
            this.xlabel.attr('y', -this.margin.left+20);
            this.xlabel.attr('x', -parseInt(this.root.attr('height'))/2);
            this.xlabel.attr('transform', 'rotate(-90)');
            this.xlabel.text(this.axis.y.label);
        } else {
            this.xlabel.attr('y', (parseInt(this.root.attr('height')) + this.margin.bottom - 15));
            this.xlabel.attr('x', parseInt(this.root.attr('width'))/2);
            this.xlabel.text(this.axis.x.label);
        }
    }

    if(this.axis.y.label.length) {
        this.ylabel = this.root.append('text');
        this.ylabel.style('font-size', '12px');
        this.ylabel.style('font-weight', 'bold');
        this.ylabel.style('font-family', 'Lato, "Lucida Grande", Arial, Helvetica, sans-serif');
        this.ylabel.attr('fill', this.ylabelcolor || 'steelblue');
        this.ylabel.attr('stroke', 'none');
        this.ylabel.style('text-anchor', 'middle');
        if(this.opts.invertAxes) {
            this.ylabel.attr('y', (parseInt(this.root.attr('height')) + this.margin.bottom - 15));
            this.ylabel.attr('x', parseInt(this.root.attr('width'))/2);
            this.ylabel.text(this.axis.x.label);
        } else {
            this.ylabel.attr('y', -this.margin.left+20);
            this.ylabel.attr('x', -parseInt(this.root.attr('height'))/2);
            this.ylabel.attr('transform', 'rotate(-90)');
            this.ylabel.text(this.axis.y.label);
        }
    }

    if(this.opts.invertAxes) {
        this.focusLine = this.focus_line_group.append('line').attr('x1', 0).attr('y1', 0).attr('x2', this.width).attr('y2', 0).attr('stroke', this.opts.theme_focus_line_color).attr('stroke-width', 1).attr('shape-rendering', 'crispEdges').style('display', 'none').attr('class', 'focus-line');
    } else {
        this.focusLine = this.focus_line_group.append('line').attr('x1', 0).attr('y1', 0).attr('x2', 0).attr('y2', this.height).attr('stroke', this.opts.theme_focus_line_color).attr('stroke-width', 1).attr('shape-rendering', 'crispEdges').style('display', 'none').attr('class', 'focus-line');
    }

    this.symbols = this.symbols_group.append('g');
    for(var name in self.charts) {
        self.charts[name].on_complete_draw();
    }

    var order = [];
    for(var name in self.charts) {
        order.push(name);
    }

    if(this.axis.x.range == 'column' || this.axis.x.range == 'bar') {
        order.reverse();
    }

    for(var i=0; i<order.length; i++) {
        var name = order[i];
        self.charts[name].setupFocus();
    }

    if(this.master_detail) {
        if(!forced) {
            this._createBrushPath();
        }
    }

    this.overlay = this.root.append('rect');
    this.overlay.attr('class', 'overlay');
    this.overlay.attr('width', this.width);
    this.overlay.attr('height', this.height);
    this.overlay.attr('fill', 'none');
    this.overlay.attr('pointer-events', 'all');

    this.overlay.attr('clip-path', 'url(#'+this.master_id+'-clip)');

    var over = function() {
        $(window).trigger('rubix.sidebar.off');
        d3.event.preventDefault();
        self.focusLine.style('display', null);
        $(this).focus();
    };

    var move = function() {
        $(window).trigger('rubix.sidebar.off');
        d3.event.preventDefault();
        $(this).focus();
        if(!self.hasData) return;
        if(self.is_touch_device) {
            var coordinates = d3.touches(this)[0];
        } else {
            var coordinates = d3.mouse(this);
        }
        if(self.opts.invertAxes) {
            if(coordinates[1] < 0 || coordinates[1] > self.height) return;
            self.overlayY.call(this, self, coordinates);
        } else {
            if(coordinates[0] < 0 || coordinates[0] > self.width) return;
            self.overlayX.call(this, self, coordinates);
        }
    };

    var out = function() {
        $(window).trigger('rubix.sidebar.on');
        d3.event.preventDefault();
        $(this).focus();
        self.tooltip.hide();
        for(var name in self.charts) {
            try {
                self.charts[name].focus.style('display', 'none');
                self.charts[name].off_focus();
            } catch(e) {
                // do nothing
            }
        }
        self.focusLine.style('display', 'none');
    };

    if(self.is_touch_device) {
        if(window.navigator.msPointerEnabled) {
            this.overlay.on('MSPointerDown', over);
            this.overlay.on('MSPointerMove', move);
            this.overlay.on('MSPointerUp' , out);
        } else {
            this.overlay.on('touchstart', over);
            this.overlay.on('touchmove', move);
            this.overlay.on('touchend' , out);
        }
    } else {
        this.overlay.on('mouseover', over);
        this.overlay.on('mousemove', move);
        this.overlay.on('mouseout' , out);
    }
};

Rubix.prototype.runCommand = function(cmd) {
    for(var i in this.charts) {
        try {
            this.charts[i][cmd](this);
        } catch(e) {
            // do nothing
        }
    }
};

Rubix.prototype.resetLabelHandlers = function() {
    var self = this;
    $('.'+this.master_id+'-legend-labels').css({
        'opacity': 0.9
    });

    $('.'+this.master_id+'-legend-labels').off().on({
        'hover': function(e) {
            var hasClass = $(this).hasClass('toggle');

            if(hasClass) return;

            switch(e.type) {
                case 'mouseenter':
                    $(this).css({
                        'opacity': 1
                    });
                break;
                case 'mouseleave':
                    $(this).css({
                        'opacity': 0.9
                    });
                break;
                default:
                break;
            }
        },
        'click': function(e) {
            var hasClass = $(this).hasClass('toggle');
            var name = $(this).attr('data-name');
            var type = $(this).attr('data-type') || '';

            if(!hasClass) {
                $(this).css({
                    'opacity': 0.2
                });
                $(this).addClass('toggle');

                var chart = self.charts[name];
                chart.hidden();
                delete self.charts[name];
                self.chart_stack[name] = chart;

                if(type.length) {
                    var fetchData = self.column_stack_data;
                    var pushData = self.column_stack_data_stack;
                    if(type === 'astack') {
                        fetchData = self.area_stack_data;
                        pushData = self.area_stack_data_stack;
                    }
                    var rdata, orderKey = 0;
                    for(var i=0; i<fetchData.length; i++) {
                        if(!fetchData[i]) continue;
                        var fname = fetchData[i].key;
                        if(fname === name) {
                            orderKey = fetchData[i].orderKey;
                            rdata = fetchData[i];
                            fetchData.splice(i, 1);
                            break;
                        }
                    }

                    pushData.splice(orderKey, 0, rdata);
                }

                var data = self.data[name];
                self.data_stack[name] = data;
                delete self.data[name];
            } else {
                $(this).css({
                    'opacity': 1
                });
                $(this).removeClass('toggle');

                var chart = self.chart_stack[name];
                self.charts[name] = chart;
                delete self.chart_stack[name];

                if(type.length) {
                    var pushedData = self.column_stack_data_stack;
                    var origData = self.column_stack_data;
                    if(type === 'astack') {
                        pushedData = self.area_stack_data_stack;
                        origData = self.area_stack_data;
                    }
                    var rdata, orderKey = 0;
                    for(var i=0; i<pushedData.length; i++) {
                        if(!pushedData[i]) continue;
                        var fname = pushedData[i].key;
                        if(fname === name) {
                            orderKey = pushedData[i].orderKey;
                            rdata = pushedData[i];
                            pushedData.splice(i, 1);
                            break;
                        }
                    }

                    origData.splice(orderKey, 0, rdata);
                }

                var data = self.data_stack[name];
                self.data[name] = data;
                delete self.data_stack[name];
                self.charts[name].show();
            }

            /** TODO: Investigate why draw needs to be called twice */
            self.draw();
            self.draw();
        }
    });
};

/**
 * @param {?Object} opts
 * @return {Rubix.LineSeries}
 */
Rubix.prototype.line_series = function(opts) {
    opts = opts || {};
    opts.name  = opts.name || this._generate_name();
    if(this.charts.hasOwnProperty(opts.name)) {
        throw new Error("Series exists: " + name);
    }
    var line_series = new Rubix.LineSeries(this, opts);
    this.charts[line_series.name] = line_series;
    this.legend.append("<div class='"+this.master_id+"-legend-labels' data-name='"+line_series.name+"' style='cursor: pointer; display: inline-block; font-weight: bold; margin-right: 10px; color: "+line_series.opts.color+"'><span style='font-size: 22px;'>■ </span><span style='font-size: 12px; position:relative; top: -3px; left: -2px;'>"+line_series.name+"</span></div>");
    this.resetLabelHandlers();
    return line_series;
};

/**
 * @param {?Object} opts
 * @return {Rubix.AreaSeries}
 */
Rubix.prototype.area_series = function(opts) {
    opts = opts || {};
    opts.name  = opts.name || this._generate_name();
    if(this.charts.hasOwnProperty(opts.name)) {
        throw new Error("Series exists: " + name);
    }
    if(this.stacked) {
        var stacked_area_series = new Rubix.StackedAreaSeries(this, opts);
        this.charts[stacked_area_series.name] = stacked_area_series;
        this.legend.append("<div class='"+this.master_id+"-legend-labels' data-name='"+stacked_area_series.name+"' data-type='astack' style='cursor: pointer; display: inline-block; font-weight: bold; margin-right: 10px; color: "+stacked_area_series.opts.color+"'><span style='font-size: 22px;'>■ </span><span style='font-size: 12px; position:relative; top: -3px; left: -2px;'>"+stacked_area_series.name+"</span></div>");
        this.resetLabelHandlers();
        return stacked_area_series;
    }
    var area_series = new Rubix.AreaSeries(this, opts);
    this.charts[area_series.name] = area_series;
    this.legend.append("<div class='"+this.master_id+"-legend-labels' data-name='"+area_series.name+"' style='cursor: pointer; display: inline-block; font-weight: bold; margin-right: 10px; color: "+area_series.opts.color+"'><span style='font-size: 22px;'>■ </span><span style='font-size: 12px; position:relative; top: -3px; left: -2px;'>"+area_series.name+"</span></div>");
    this.resetLabelHandlers();
    return area_series;
};

Rubix.prototype._generate_name = function() {
    return 'Series ' + (++this.chart_counter);
};

/**
 * @param {?Object} opts
 * @return {Rubix.ColumnSeries}
 */
Rubix.prototype.column_series = function(opts) {
    opts = opts || {};
    opts.name  = opts.name || this._generate_name();
    if(this.charts.hasOwnProperty(opts.name)) {
        throw new Error("Series exists: " + name);
    }
    if(this.opts.invertAxes !== false || this.opts.axis.x.range !== 'column') {
        this.opts.invertAxes = false;
        this.opts.axis.x.type = 'ordinal';
        this.opts.axis.x.range = 'column';
        this.setup()
    }
    var column_series = new Rubix.ColumnSeries(this, opts);
    this.charts[column_series.name] = column_series;
    this.legend.append("<div class='"+this.master_id+"-legend-labels' data-name='"+column_series.name+"' data-type='cstack' style='cursor: pointer; display: inline-block; font-weight: bold; margin-right: 10px; color: "+column_series.opts.color+"'><span style='font-size: 22px;'>■ </span><span style='font-size: 12px; position:relative; top: -3px; left: -2px;'>"+column_series.name+"</span></div>");
    this.resetLabelHandlers();
    return column_series;
};

/**
 * @param {?Object} opts
 * @return {Rubix.ColumnSeries}
 */
Rubix.prototype.bar_series = function(opts) {
    opts = opts || {};
    opts.name  = opts.name || this._generate_name();
    if(this.charts.hasOwnProperty(opts.name)) {
        throw new Error("Series exists: " + name);
    }
    if(this.opts.invertAxes !== true || this.opts.axis.x.range !== 'column') {
        this.opts.invertAxes = true;
        this.opts.axis.x.type = 'ordinal';
        this.opts.axis.x.range = 'column';
        this.setup()
    }
    var column_series = new Rubix.ColumnSeries(this, opts);
    this.charts[column_series.name] = column_series;
    this.legend.append("<div class='"+this.master_id+"-legend-labels' data-name='"+column_series.name+"' data-type='cstack' style='cursor: pointer; display: inline-block; font-weight: bold; margin-right: 10px; color: "+column_series.opts.color+"'><span style='font-size: 22px;'>■ </span><span style='font-size: 12px; position:relative; top: -3px; left: -2px;'>"+column_series.name+"</span></div>");
    this.resetLabelHandlers();
    return column_series;
};

window.Rubix = window.Rubix || {};
/**
 * @param {Rubix} rubix
 * @param {Object} opts
 * @constructor
 */
Rubix.StackedAreaSeries = function(rubix, opts) {
    this.opts = opts;
    this.opts.color = this.opts.color || 'steelblue';
    this.opts.marker = this.opts.marker || 'circle';
    this.opts.fillopacity = this.opts.fillopacity || 0.5;
    this.opts.strokewidth = this.opts.strokewidth || 1;
    this.opts.noshadow = this.opts.noshadow || false;
    this.show_markers = this.opts.show_markers;

    if(!this.opts.hasOwnProperty('name')) throw new Error('StackedAreaSeries should have a \'name\' property');

    this.name = this.opts.name;

    this.chart_hidden = false;
    this.temp_stack = [];

    this.setup = false;
    this.last_stack = [];
    this.last_stack_name = '';
    this.last_stack_added = false;

    this._setup(rubix);
};

/**
 * @param {Rubix} rubix
 * @private
 */
Rubix.StackedAreaSeries.prototype._setup = function(rubix) {
    this.rubix = rubix;

    this.root   = this.rubix.root;
    this.data   = this.rubix.data;
    this.width  = this.rubix.width;
    this.height = this.rubix.height;
    this.stack  = this.rubix.area_stack;
    this.offset = this.rubix.area_offset;
    this.area_stack_data = this.rubix.area_stack_data;
    this.stacked_area_series = this.rubix.root_stacked_area_series;
    this.show_markers = (this.show_markers === undefined) ? this.rubix.show_markers : this.show_markers;

    this.master_detail = this.rubix.master_detail;

    if(this.master_detail) {
        this.md_root = this.rubix.md_root;
    }

    if(!this.id) {
        this.id = this.rubix.uid('stacked-area');
    }

    /** separator */
    var self = this;
    this.line = d3.svg.line();
    this.line.defined(function(d) {
        if(self.rubix.offset === 'expand') {
            if(d.y_new === 0 && d.y0 === 1) {
                return false;
            }
            return true;
        }

        return d.y_new !== null && d.x !== null;
    });
    if(this.rubix.opts.invertAxes) {
        this.line.x(function(d) {
            var val = d.y0 + d.y_new;
            if(isNaN(val)) {
                return 0;
            }
            return self.rubix.x(val);
        });
        this.line.y(function(d) {
            if(self.rubix.axis.y.range === 'column' || self.rubix.axis.y.range === 'bar') {
                return self.rubix.y(d.x) + self.rubix.y.rangeBand()/2;
            }
            return self.rubix.y(d.x);
        });
        this.line.interpolate(this.rubix.interpolate);
    } else {
        this.line.x(function(d) {
            if(self.rubix.axis.x.range === 'column' || self.rubix.axis.x.range === 'bar') {
                return self.rubix.x(d.x) + self.rubix.x.rangeBand()/2;
            }
            return self.rubix.x(d.x);
        });
        this.line.y(function(d) {
            var val = d.y0 + d.y_new;
            if(isNaN(val)) {
                return 0;
            }
            return self.rubix.y(val);
        });
        this.line.interpolate(this.rubix.interpolate);
    }

    this.area = d3.svg.area();
    this.area.defined(this.line.defined());

    if(this.rubix.opts.invertAxes) {
        this.area.x0(function(d) {
            var val = d.y0;
            if(isNaN(val)) {
                return 0;
            }
            return self.rubix.x(val);
        });
        this.area.x1(function(d) {
            var val = d.y0 + d.y_new;
            if(isNaN(val)) {
                return 0;
            }
            return self.rubix.x(val);
        });
        this.area.y(function(d) {
            if(self.rubix.axis.y.range === 'column' || self.rubix.axis.y.range === 'bar') {
                return self.rubix.y(d.x) + self.rubix.y.rangeBand()/2;
            }
            return self.rubix.y(d.x);
        });
        this.area.interpolate(this.rubix.interpolate);
    } else {
        this.area.x(function(d) {
            if(self.rubix.axis.x.range === 'column' || self.rubix.axis.x.range === 'bar') {
                return self.rubix.x(d.x) + self.rubix.x.rangeBand()/2;
            }
            return self.rubix.x(d.x);
        });
        this.area.y0(function(d) {
            var val = d.y0;
            if(isNaN(val)) {
                return 0;
            }
            return self.rubix.y(val);
        });
        this.area.y1(function(d) {
            var val = d.y0 + d.y_new;
            if(isNaN(val)) {
                return 0;
            }
            return self.rubix.y(val);
        });
        this.area.interpolate(this.rubix.interpolate);
    }

    if(this.master_detail) {
        this.master_line = d3.svg.line();
        this.master_line.defined(function(d) {
            if(self.rubix.area_offset === 'expand') {
                if(d.y_new === 0 && d.y0 === 1) {
                    return false;
                }
                return true;
            }

            return d.y_new !== null && d.x !== null;
        });
        this.master_line.x(function(d) {
            return self.rubix.x2(d.x);
        });
        this.master_line.y(function(d) {
            var val = d.y0 + d.y_new;
            if(isNaN(val)) {
                return 0;
            }
            return self.rubix.y2(val);
        });
        this.master_line.interpolate(this.rubix.interpolate);

        this.master_area = d3.svg.area();
        this.master_area.defined(this.master_line.defined());
        this.master_area.x(function(d) {
            return self.rubix.x2(d.x);
        });
        this.master_area.y0(function(d) {
            var val = d.y0;
            if(isNaN(val)) {
                return 0;
            }
            return self.rubix.y2(val);
        });
        this.master_area.y1(function(d) {
            var val = d.y0 + d.y_new;
            if(isNaN(val)) {
                return 0;
            }
            return self.rubix.y2(val);
        });
        this.master_area.interpolate(this.rubix.interpolate);
    }
};

// Alias for draw
Rubix.StackedAreaSeries.prototype.redraw = function(rubix) {
    this._setup(rubix);
    this.draw();
};

Rubix.StackedAreaSeries.prototype.noRedraw = function(rubix) {
    this._setup(rubix);
    this.draw(true);
};

/**
 * @param {Array|Object} data
 */
Rubix.StackedAreaSeries.prototype.addData = function(data) {
    this.rubix.data_changed = true;
    if(!(data instanceof Array)) {
        if(!(data instanceof Object)) {
            throw new Error("Data must be an array or object");
        } else {
            if(!(data.hasOwnProperty('x') || data.hasOwnProperty('y'))) {
                throw new Error("Object must be in the form: {x: ..., y: ...}");
            }

            data = [data];
        }
    }

    if(!data.length) return;

    if(!this.rubix.opts.noSort) {
        data.sort(function(a, b) {
            if(a.x > b.x) {
                return 1;
            } else if(a.x === b.x) {
                return 0;
            } else {
                return -1;
            }
        });
    }

    this.data[this.name] = data;

    this.area_stack_data.push({
        key: this.name,
        color: this.opts.color,
        values: this.data[this.name],
        fillopacity: this.opts.fillopacity,
        strokewidth: this.opts.strokewidth,
        orderKey: this.area_stack_data.length
    });

    this.rubix.resetAxis(true);
    this.rubix.forceRedraw();
    this._animate_draw();
};

Rubix.StackedAreaSeries.prototype.draw = function(forced) {
    if(!this.name) return;
    if(!this.data) return;
    if(!this.data.hasOwnProperty(this.name)) return;
    if(!this.data[this.name].length) return;

    var oldLayers = this.layers;
    try {
        this.layers = this.stack(this.area_stack_data);
    } catch(e) {
        // data un-available. retaining old layer.
        this.layers = oldLayers;
    }

    var self = this;

    var isConstructed = this.stacked_area_series.selectAll('.'+this.id)[0].length;

    if(!isConstructed) {
        try {
            this.stacked_area_series.selectAll('.layer').remove();
            this.stacked_area_series.selectAll('.' + this.id + '-line').remove();

            var p = this.stacked_area_series.selectAll('.layer').data(this.layers, function(d) {
                return d.key;
            });

            var createdPath = p.enter().append('path');
            createdPath.attr('class', 'layer')
                        .attr('d', function(d) { return self.area(d.values); })
                        .attr('fill', function(d) { return d.color; })
                        .attr('fill-opacity', function(d) {
                            return d.fillopacity
                        })
                        .attr('stroke', 'none');

            p.exit().remove();

            if(this.master_detail) {
                createdPath.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');
                createdPath.classed('clipped', true);

                if(!forced) {
                    var md_p = this.md_root.select('.md-layers > .md_stacked_area_series').selectAll('.layer').data(this.layers, function(d) {
                        return d.key;
                    });

                    var createdMdPath = md_p.enter().append('path');
                    createdMdPath.attr('class', 'layer')
                                 .attr('d', function(d) { return self.master_area(d.values); })
                                 .attr('fill', function(d) { return d.color; })
                                 .attr('fill-opacity', function(d) {
                                    return d.fillopacity;
                                 })
                                 .attr('stroke', 'none');

                    createdMdPath.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');

                    createdMdPath.classed('clipped', true);

                    md_p.exit().remove();
                }
            }

            for(var i=0; i<this.layers.length; i++) {
                var name = this.layers[i].key;
                if(this.name === name) {
                    var datum = this.layers[i].values;
                    if(!this.opts.noshadow) {
                        this.strokePath1 = this.stacked_area_series.append('path').attr('class', this.id+'-line').datum(datum).attr('d', this.line).attr('stroke', 'black').attr('fill', 'none').attr('stroke-linecap', 'round').attr('stroke-width', 5 * this.opts.strokewidth).attr('stroke-opacity', 0.05000000000000001).attr('transform', 'translate(1,1)');
                        this.strokePath2 = this.stacked_area_series.append('path').attr('class', this.id+'-line').datum(datum).attr('d', this.line).attr('stroke', 'black').attr('fill', 'none').attr('stroke-linecap', 'round').attr('stroke-width', 3 * this.opts.strokewidth).attr('stroke-opacity', 0.1).attr('transform', 'translate(1,1)');
                        this.strokePath3 = this.stacked_area_series.append('path').attr('class', this.id+'-line').datum(datum).attr('d', this.line).attr('stroke', 'black').attr('fill', 'none').attr('stroke-linecap', 'round').attr('stroke-width', 1 * this.opts.strokewidth).attr('stroke-opacity', 0.15000000000000002).attr('transform', 'translate(1,1)');
                    }
                    this.linePath = this.stacked_area_series.append('path').datum(datum).attr('class', this.id+'-line').attr('stroke', this.opts.color).attr('fill', 'none').attr('stroke-linecap', 'round').attr('d', this.line).attr('stroke-width', 2 * this.opts.strokewidth);

                    if(this.master_detail) {
                        if(!forced) {
                            this.md_root.select('.md-layers > .md_stacked_area_series').selectAll('.' + this.id + '-line').remove();
                            this.masterLinePath = this.md_root.select('.md-layers > .md_stacked_area_series').append('path').datum(datum).attr('class', this.id+'-line').attr('stroke', this.opts.color).attr('fill', 'none').attr('stroke-linecap', 'round').attr('d', this.master_line).attr('stroke-width', 2 * this.opts.strokewidth);
                            this.masterLinePath.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');
                            this.masterLinePath.classed('clipped', true);

                            if(this.dataChanged) {
                                this.rubix.resetExtent();
                                this.dataChanged = false;
                            }
                        }

                        if(!this.opts.noshadow) {
                            this.strokePath1.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');
                            this.strokePath2.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');
                            this.strokePath3.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');
                        }
                        this.linePath.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');

                        if(!this.opts.noshadow) {
                            this.strokePath1.classed('clipped', true);
                            this.strokePath2.classed('clipped', true);
                            this.strokePath3.classed('clipped', true);
                        }
                        this.linePath.classed('clipped', true);
                    }
                    break;
                }
            }
        } catch(e) {
            // do nothing
        }
        this.setup = true;
    } else {
        this.rubix.runCommand('globalRedraw');
    }

    this.rubix.resetFocus(forced);
};

/**
 * @param {Object} data
 * @param {?Boolean} shift
 * @param {?Boolean} noRedraw
 */
Rubix.StackedAreaSeries.prototype.updatePoint = function(data, shift, noRedraw) {
    this.addPoint(data, shift, noRedraw);
};

/**
 * @param {*} ref
 * @param {?Boolean} noRedraw
 */
Rubix.StackedAreaSeries.prototype.removePoint = function(ref, noRedraw) {
    if(this.chart_hidden) {
        this.temp_stack.push({
            type: 'removePoint',
            ref: ref
        });
        return;
    }

    if(!this.data[this.name]) {
        this.data[this.name] = [];
    }

    var removed = false, pos = 0;
    for(var i=0; i<this.data[this.name].length; i++) {
        if(this.data[this.name][i].x === ref) {
            this.data[this.name][i].y = null;
            pos = i;
            removed = true;
            break;
        }
    }

    if(!removed) return;

    var found = false;
    for(var i=0; i<this.area_stack_data.length; i++) {
        var st = this.area_stack_data[i];
        try {
            if(st.values[pos].x === ref) {
                if(st.values[pos].y !== null) {
                    found = true;
                    break;
                }
            }
        } catch(e) {
            // do nothing
        }
    }

    if(!found) {
        for(var i=0; i<this.area_stack_data.length; i++) {
            this.area_stack_data[i].values.splice(pos, 1);
        }
    }

    var oldLayers = this.layers;
    try {
        this.layers = this.stack(this.area_stack_data);
    } catch(e) {
        // data un-available. retaining old layer.
        this.layers = oldLayers;
    }

    if(this.master_detail) {
        this.dataChanged = true;
    }

    if(noRedraw) return;

    if(this.setup) {
        this._animate_draw();
    } else {
        this.rubix.resetAxis(true);
        this.rubix.forceRedraw();
    }
};

/**
 * @param {Object} data
 * @param {?Boolean} shift
 * @param {?Boolean} noRedraw
 */
Rubix.StackedAreaSeries.prototype.addPoint = function(data, shift, noRedraw) {
    this.rubix.data_changed = true;
    if(!(data instanceof Object) || (data instanceof Array)) {
        throw new Error("Object required for addPoint");
    }
    if(!(data.hasOwnProperty('x') || data.hasOwnProperty('y'))) {
        throw new Error("Object must be in the form: {x: ..., y: ...}");
    }

    if(this.chart_hidden) {
        this.temp_stack.push({
            type: 'addPoint',
            data: data,
            shift: shift
        });
        return;
    }

    if(!this.data[this.name]) {
        this.data[this.name] = [];
    }

    var added = false;
    for(var i=0; i<this.data[this.name].length; i++) {
        if(this.data[this.name][i].x === data.x) {
            this.data[this.name][i].y = data.y;
            added = true;
            break;
        }
    }

    if(!added) {
        this.data[this.name].push(data);
    }

    this.data[this.name].sort(function(a, b) {
        if(a.x > b.x) {
            return 1;
        } else if(a.x === b.x) {
            return 0;
        } else {
            return -1;
        }
    });

    if(this.rubix.opts.interval) {
        if(this.rubix.opts.interval < this.data[this.name].length) {
            this.data[this.name].shift();
        }
    } else {
        if(shift) {
            this.data[this.name].shift();
        }
    }

    var max_elems = d3.max(this.area_stack_data, function(d) {
        return d.values.length;
    });

    for(var i=0; i<this.area_stack_data.length; i++) {
        var st = this.area_stack_data[i];
        if(st.values.length < max_elems) {
            st.values.push({
                x: data.x,
                y: null
            });
        }
    }

    var oldLayers = this.layers;
    try {
        this.layers = this.stack(this.area_stack_data);
    } catch(e) {
        // data un-available. retaining old layer.
        this.layers = oldLayers;
    }

    if(this.master_detail) {
        this.dataChanged = true;
    }

    if(noRedraw) return;

    if(this.setup) {
        this._animate_draw();
    } else {
        this.rubix.resetAxis(true);
        this.rubix.forceRedraw();
    }
};

Rubix.StackedAreaSeries.prototype._animate_draw = function() {
    var text = this.root.selectAll('.y.axis').selectAll('text')[0];
    var width = [];
    for(var i=0; i<text.length; i++) {
        width.push(text[i].getBBox().width);
    }
    var origMaxWidth = d3.max(width);

    this.rubix.resetAxis(true);

    this.rubix.runCommand('globalRedraw');

    text = this.root.selectAll('.y.axis').selectAll('text')[0];
    width = [];
    for(var i=0; i<text.length; i++) {
        width.push(text[i].getBBox().width);
    }

    var maxWidth = d3.max(width);

    this.rubix.marginLeft(maxWidth);
    this.rubix.resetFocus();
};

Rubix.StackedAreaSeries.prototype.hidden = function() {
    this.chart_hidden = true;
};

Rubix.StackedAreaSeries.prototype.show = function() {
    this.chart_hidden = false;
    while(this.temp_stack.length>1) {
        var rawData = this.temp_stack.shift();
        if(rawData.type === 'addPoint') {
            this.addPoint(rawData.data, rawData.shift, true);
        } else {
            this.removePoint(rawData.ref);
        }
    }
    if(this.temp_stack.length) {
        var rawData = this.temp_stack.shift();
        if(rawData.type === 'addPoint') {
            this.addPoint(rawData.data, rawData.shift, true);
        } else {
            this.removePoint(rawData.ref);
        }
    }
};

Rubix.StackedAreaSeries.prototype.globalRedraw = function(rubix) {
    this.stacked_area_series.select('.'+this.id+'-line').attr('d', this.line);
};

Rubix.StackedAreaSeries.prototype.forceRedraw = function(rubix) {
    this.redraw(rubix);
};

Rubix.StackedAreaSeries.prototype.on_complete_draw = function() {
    if(!this.setup) return;
    var self = this;
    if(this.opts.marker !== undefined && this.show_markers) {
        if(this.master_detail) {
            this.rubix.symbols.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip-symbols)');
        }
        this.markers = this.rubix.symbols.selectAll('.'+this.id+'-marker');
        switch(this.opts.marker) {
            case 'circle':
            case 'cross':
            case 'square':
            case 'diamond':
            case 'triangle-up':
            case 'triangle-down':
                var symbol = d3.svg.symbol();
                symbol.type(this.opts.marker);

                var symbolType = this.markers.data(this.data[this.name]);
                var symbolPath = symbolType.enter().append('path');
                symbolPath.attr('d', symbol)
                      .attr('class', this.id + '-marker')
                      .attr('fill', this.opts.color)
                      .style('display', function(d) {
                            if(self.rubix.area_offset === 'expand') {
                                if(d.y_new === 0 && d.y0 === 1) {
                                    return 'none';
                                }
                                return null;
                            }
                            if(d.y_new === null) {
                                return 'none';
                            }
                            return null;
                      })
                      .attr('stroke', 'white')
                      .attr('transform', function(d) {
                            var val = d.y0 + d.y_new;
                            if(isNaN(val)) {
                                val = 0;
                            }
                            if(self.rubix.opts.invertAxes) {
                                var _y = self.rubix.y(d.x);
                                var _x = self.rubix.x(val);
                                if(self.rubix.axis.y.range === 'column' || self.rubix.axis.y.range === 'bar') {
                                    _y += self.rubix.y.rangeBand()/2;
                                }
                            } else {
                                var _y = self.rubix.y(val);
                                var _x = self.rubix.x(d.x);
                                if(self.rubix.axis.x.range === 'column' || self.rubix.axis.x.range === 'bar') {
                                    _x += self.rubix.x.rangeBand()/2;
                                }
                            }
                            return 'translate('+_x+','+_y+')';
                      });
                symbolType.exit().remove();
            break;
            default:
                throw new Error('Unknown marker type : ' + this.opts.marker);
            break;
        }
    }
};

Rubix.StackedAreaSeries.prototype.setupFocus = function() {
    if(!this.setup) return;
    if(this.focus) this.focus.remove();
    this.focus = this.rubix.focus_group.append('g');
    this.focus.attr('class', 'focus');
    this.focus.style('display', 'none');
    switch(this.opts.marker) {
        case 'circle':
        case 'cross':
        case 'square':
        case 'diamond':
        case 'triangle-up':
        case 'triangle-down':
            var symbol = d3.svg.symbol();
            symbol.type(this.opts.marker);
            var path = this.focus.append('path').attr('d', symbol).attr('fill', this.opts.color).attr('stroke', 'white').attr('stroke-width', 2);
        break;
        default:
            throw new Error('Unknown marker type : ' + this.opts.marker);
        break;
    }
};

Rubix.StackedAreaSeries.prototype.on_focus = function() {
    if(!this.setup) return;
    this.linePath.attr('stroke-width', 2 * this.opts.strokewidth);
};

Rubix.StackedAreaSeries.prototype.off_focus = function() {
    if(!this.setup) return;
    this.linePath.attr('stroke-width', 2 * this.opts.strokewidth);
};
window.Rubix = window.Rubix || {};

var slugify = function(text) {
    text = text.replace(/[^-a-zA-Z0-9,&\s]+/ig, '');
    text = text.replace(/-/gi, "_");
    text = text.replace(/\s/gi, "-");
    return text;
};

/**
 * @param {string} id
 * @param {Object} opts
 * @return {Rubix.PieDonut}
 */
Rubix.Pie = function(id, opts) {
    return new Rubix.PieDonut(id, 'pie', opts);
};

/**
 * @param {string} id
 * @param {Object} opts
 * @return {Rubix.PieDonut}
 */
Rubix.Donut = function(id, opts) {
    return new Rubix.PieDonut(id, 'donut', opts);
};

/**
 * @private
 * @param {string} id
 * @param {Rubix.Pie || Rubix.Donut} type
 * @param {Object} opts
 * @constructor
 */
Rubix.PieDonut = function(id, type, opts) {
    opts = opts || {};
    this.type = type;
    this.is_touch_device = 'ontouchstart' in document.documentElement;

    this.legend_hidden = {};

    this.chart_counter = 0;
    this.master_id = this.uid('master_id');

    this.area_stack_data_stack = [];
    this.column_stack_data_stack = [];

    this.root_elem = $(id);
    this.root_elem.css('position', 'relative');
    this.root_elem.addClass('rubixccnium-main-chart');
    this.root_elem.append('<div class="rubixccnium-tooltip"></div>');
    this.root_elem.append('<div class="rubixccnium-title"></div>');
    this.root_elem.append('<div class="rubixccnium-subtitle"></div>');
    this.root_elem.append('<div class="rubixccnium-chart"><div style="margin-top:5px">Loading...</div></div>');
    this.root_elem.append('<div class="rubixccnium-legend"></div>');

    var width = opts.width || '100%';
    var height = opts.height || 150;
    this.elem_width = width, this.elem_height = height;
    this.root_elem.width(width).height(height);

    this.elem = this.root_elem.find('.rubixccnium-chart');

    this.tooltip = this.root_elem.find('.rubixccnium-tooltip');
    this.tooltip.hide();
    this.tooltip.html("");

    opts.tooltip = opts.tooltip || {};

    this.tooltip.css({
        'font-family': 'Lato, "Lucida Grande", Arial, Helvetica, sans-serif',
        'font-size': '12px',
        'position': 'absolute',
        'background': 'white',
        'color': '#89949B',
        'display': 'none',
        'padding': '5px 15px',
        'pointer-events': 'none',
        'border-radius': '5px',
        'z-index': 100,
        'min-height': 50,
        'user-select': 'none',
        'cursor': 'default',
        'border': '3px solid ' + (opts.tooltip.color ? opts.tooltip.color : '#89949B'),
        'box-shadow': 'rgba(0, 0, 0, 0.2) 2px 4px 8px',
        'background': 'white'
    });

    opts.legend = opts.legend || {};

    this.legend = this.root_elem.find('.rubixccnium-legend');
    this.legend.css({
        'font-family': "Lato, 'Lucida Grande', Arial, Helvetica, sans-serif",
        'text-align': 'center',
        'margin-top': opts.legend.top || '-10px',
        'margin-bottom': opts.legend.bottom || '5px',
        'user-select': 'none',
        'display': opts.hideLegend ? 'none' : 'block'
    });

    this.title = this.root_elem.find('.rubixccnium-title');
    this.subtitle = this.root_elem.find('.rubixccnium-subtitle');

    this.title.css({
        'font-family': "Lato, 'Lucida Grande', Arial, Helvetica, sans-serif",
        'text-align': 'center',
        'user-select': 'none',
        'font-weight': 'bold',
        'font-size': '16px',
        'color': 'steelblue',
        'margin-top': '10px',
        'cursor': 'default'
    });

    this.subtitle.css({
        'font-family': "Lato, 'Lucida Grande', Arial, Helvetica, sans-serif",
        'text-align': 'center',
        'user-select': 'none',
        'font-size': '10px',
        'color': 'steelblue',
        'margin-top': '10px',
        'cursor': 'default',
        'opacity': 0.8
    });

    var self = this;

    this.elem.css({
        'width': '100%',
        'height': parseInt(this.root_elem.get(0).style.height),
        'user-select': 'none',
        'cursor': 'default'
    });
    this.root_elem.css('height', '100%');
    this.opts = opts || {};

    this.data = [];
    this.is_touch_device = 'ontouchstart' in document.documentElement;
    this.d3_eventSource = function () {
        var e = d3.event, s;
        while (s = e.sourceEvent) e = s;
        return e;
    }

    this.last_render = null;
    this.data_changed = false;
    this.setup();
};

Rubix.PieDonut.prototype.setup = function() {
    this._setupOpts();
    this._setupOnce();
    this._setupRedraw();

    this.draw();
};


/** @private */
Rubix.PieDonut.prototype._setupOpts = function() {
    this.opts.theme_style = this.opts.theme_style || 'light';
    this.opts.theme_style_color = (this.opts.theme_style === 'light') ? '#C0D0E0' : '#555';
    this.opts.theme_focus_line_color = (this.opts.theme_style === 'light') ? '#C0D0E0' : '#888';

    this.opts.legend_color_brightness = this.opts.legend_color_brightness || 0.5;
    this.opts.global_legend_color = this.opts.global_legend_color || false;

    this.opts.titleColor = this.opts.titleColor || 'steelblue';
    this.opts.subtitleColor = this.opts.subtitleColor || 'steelblue';

    this.title.css('color', this.opts.titleColor);
    this.subtitle.css('color', this.opts.subtitleColor);

    if(this.opts.theme_style === 'dark') {
        this.tooltip.css({
            "color": "#aaa",
            "font-weight": "bold",
            "border": "1px solid #222",
            "background-color": "#303030"
        });
    }

    this.opts.margin = this.opts.margin || {};
    this.margin = {
        top    : this.opts.margin.top    || 25,
        left   : this.opts.margin.left   || 25,
        right  : this.opts.margin.right  || 25,
        bottom : this.opts.margin.bottom || 25
    };

    this.opts.tooltip = this.opts.tooltip || {};
    this.opts.tooltip.format = this.opts.tooltip.format || {};
    this.tooltipFormatter = {
        format: {
            x: this.opts.tooltip.format.x || '',
            y: this.opts.tooltip.format.y || ''
        }
    }

    this.resize = this.opts.resize || 'throttled';

    this.opts.title = this.opts.title || '';
    this.opts.subtitle = this.opts.subtitle || '';

    this.title.html(this.opts.title);
    this.subtitle.html(this.opts.subtitle);
    if(this.opts.title.length || this.opts.subtitle.length) {
        this.elem.css('margin-top', '-20px');
    }
    (this.opts.title.length) ? this.title.show() : this.title.hide();
    (this.opts.subtitle.length) ? this.subtitle.show() : this.subtitle.hide();
};

/** @private */
Rubix.PieDonut.prototype._setupOnce = function() {
    // Add Pie Stack data here.
};

Rubix.PieDonut.prototype._setupRedraw = function() {
    var self = this;

    $(window).on('orientationchange', function() {
        self.draw();
    });

    $(window).on('rubix.redraw.'+this.master_id, function() {
        self.draw();
    });

    switch(this.resize) {
        case 'always':
            $(window).on('resize', function() {
                self.draw();
            });
        break;
        case 'debounced':
            $(window).on('debouncedresize', function() {
                self.draw();
            });
        break;
        case 'throttled':
            $(window).on('throttledresize', function() {
                self.draw();
            });
        break;
        default:
            throw new Error('Unknown resize type!');
        break;
    }
};

Rubix.PieDonut.prototype.draw = function() {
    this._draw();
};

/**
 * @private
 */
Rubix.PieDonut.prototype._draw = function() {
    this._cleanElement();
    this._setSize();
    this._setupCanvas();
    this._setupRoot();
    this._setupSeries();
    this.final_draw();
};

/** @private */
Rubix.PieDonut.prototype._cleanElement = function() {
    this.elem.children().remove();
};

/** @private */
Rubix.PieDonut.prototype._setSize = function() {
    this.outerWidth  = this.elem.width();
    this.outerHeight = this.elem.height();

    this.width  = this.outerWidth - this.margin.left - this.margin.right;
    this.height = this.outerHeight - this.margin.top - this.margin.bottom;

    this.innerRadius = 0;
    this.outerRadius = (Math.min(this.width, this.height) * .5) - 10;

    if(this.type === 'donut') {
        this.innerRadius = this.outerRadius * .6;
    }
};

/** @private */
Rubix.PieDonut.prototype._setupCanvas = function() {
    this.elem.html('');
    this.canvas = d3.select(this.elem.get(0)).append('svg');
    this.canvas.attr('width', this.outerWidth);
    this.canvas.attr('height', this.outerHeight);

    this.canvas.append('desc').text("Powered by RubixÆžium");
};

/** @private */
Rubix.PieDonut.prototype._setupRoot = function() {
    this.root = this.canvas.append('g');
    this.root.attr('width', this.width);
    this.root.attr('height', this.height);
    this.root.attr('transform', 'translate('+this.margin.left+','+this.margin.top+')');

    if(!this.hasData) {
        this.canvas.append('text').attr('class', 'noData').attr('font-size', '30px').attr('text-anchor', 'middle').attr('transform', 'translate('+this.outerWidth/2+','+this.outerHeight/2+')').attr('font-family', 'Lato, "Lucida Grande", Arial, Helvetica, sans-serif').text("No Data");
    } else {
        this.canvas.select('.noData').style('display', 'none');
    }
};

Rubix.PieDonut.prototype._setupSeries = function() {
    this.root_piedonut_series = this.root.append('g').attr('class', 'piedonut_series').attr('transform', "translate(" + this.width / 2 + "," + this.height / 2 + ")");
};

Rubix.PieDonut.prototype.uid = function(type) {
    return 'rubixcc-pie-donut-' + type +'-' + Math.floor(2147483648*Math.random()).toString(36);
};

Rubix.PieDonut.prototype.addData = function(data) {
    this.data_changed = true;
    data.forEach(function(d) {
        d.value = +d.value;
    });

    this.data = data;

    this._setupLegend();
    this.final_draw('launch');
};

Rubix.PieDonut.prototype._setupLegend = function() {
    var self = this;
    this.legend.children().remove();
    for(var i=0; i<this.data.length; i++) {
        var color = this.opts.global_legend_color || this.data[i].color;
        this.legend.append("<div class='"+this.master_id+"-legend-labels' data-name='"+this.data[i].name+"' style='cursor: pointer; display: inline-block; font-weight: bold; margin-right: 10px; color: "+color+"'><span style='font-size: 22px;'>■ </span><span style='font-size: 12px; position:relative; top: -3px; left: -2px;'>"+this.data[i].name+"</span></div>");
    }

    $('.'+this.master_id+'-legend-labels').css({
        'opacity': 0.9
    });

    for(var id in this.legend_hidden) {
        var name = this.legend_hidden[id];
        $('[data-name='+name+']').css({
            'opacity': 0.2
        }).addClass('toggle');
    }

    $('.'+this.master_id+'-legend-labels').off().on({
        'hover': function(e) {
            var hasClass = $(this).hasClass('toggle');
            var name = $(this).attr('data-name');
            var id = '#'+self.master_id+'-'+slugify(name);

            if(hasClass) return;

            switch(e.type) {
                case 'mouseenter':
                    $(this).css({
                        'opacity': 1
                    });
                    d3.select(id).node().__onmouseover(e);
                break;
                case 'mouseleave':
                    $(this).css({
                        'opacity': 0.9
                    });
                    d3.select(id).node().__onmouseout(e)
                break;
                default:
                break;
            }
        },
        'click': function(e) {
            var hasClass = $(this).hasClass('toggle');
            var name = $(this).attr('data-name');
            var id = '#'+self.master_id+'-'+slugify(name);

            if(!hasClass) {
                $(this).css({
                    'opacity': 0.2
                });
                $(this).addClass('toggle');
                $(id).fadeOut();
                self.legend_hidden[id] = name;
            } else {
                $(this).css({
                    'opacity': 1
                });
                $(this).removeClass('toggle');
                $(id).fadeIn();
                delete self.legend_hidden[id];
            }
        }
    });
};

Rubix.PieDonut.prototype.final_draw = function(animate) {
    var self = this;
    if(this.data.length) {
        this.canvas.select('.noData').style('display', 'none');
    }
    var arc = d3.svg.arc();
    arc.innerRadius(this.innerRadius);
    arc.outerRadius(this.outerRadius);

    var pie = d3.layout.pie();
    pie.sort(null);
    pie.value(function(d) {
        return d.value;
    });

    var main = this.root_piedonut_series.selectAll('.arc')
                .data(pie(this.data), function(d) {
                    return d.data.name;
                });

    var g = main.enter().append('g')
                .attr('class', 'arc').style('position', 'relative');

    var color = d3.scale.category20();
    var path = g.attr('id', function(d) {
        return self.master_id+'-'+slugify(d.data.name);
    }).style("fill", function(d, i) { return d.data.color; }).style('stroke', 'white').append("path").attr("d", function(d) {
        return arc({
            startAngle: 0,
            endAngle: 0
        });
    })

    if(animate === 'launch') {
        this.old_pie_data = pie(this.data);
        setTimeout(function() {
            path.transition().attrTween('d', function(d) {
                var i = d3.interpolate({
                    startAngle: 0,
                    endAngle: 0
                }, {
                    startAngle: d.startAngle,
                    endAngle: d.endAngle
                });
                return function(t) {
                    return arc(i(t));
                }
            }).duration(500);
        }, 15);
    } else if(animate === 'add') {
        var t = main.transition().duration(500).each('end', function() {
            self.old_pie_data = pie(self.data);
        });
        t.select('path').attrTween('d', function(d, k) {
            var startAngle = 0;
            var endAngle = 0;
            try {
                startAngle = self.old_pie_data[k].startAngle;
                endAngle = self.old_pie_data[k].endAngle;
            } catch(e) {
                startAngle = self.old_pie_data[self.old_pie_data.length-1].startAngle;
                endAngle = self.old_pie_data[self.old_pie_data.length-1].endAngle;
                // do nothing
            }
            var i = d3.interpolate({
                startAngle: startAngle,
                endAngle: endAngle
            }, {
                startAngle: d.startAngle,
                endAngle: d.endAngle
            });

            if(d.data.hasOwnProperty('_remove')) {
                self.data.splice(k, 1);
            }
            self._setupLegend();
            return function(t) {
                return arc(i(t));
            }
        });
    } else {
        this.old_pie_data = pie(this.data);
        path.attr('d', function(d) {
            return arc(d);
        });
    }

    var mouseover = 'mouseover',
        mousemove = 'mousemove',
        mouseout  = 'mouseout';
    if(this.is_touch_device) {
        mouseover = 'touchstart';
        mousemove = 'touchstart';
        mouseout  = 'touchend';
    }

    var timer;

    g.on(mouseover, function(d) {
        clearTimeout(timer);
        var centroid = arc.centroid(d);
        var h = Math.sqrt(Math.pow(centroid[0], 2) + Math.pow(centroid[1], 2));

        var x = centroid[0]/h * 8;
        var y = centroid[1]/h * 8;
        if(!self.is_touch_device) {
            d3.select(this).transition().duration(150).attr('transform', "translate(" + [x, y] + ")");
        }
    });

    g.on(mousemove, function(d, i) {
        clearTimeout(timer);
        if(self.is_touch_device) {
            var coordinates = d3.touches(this, self.d3_eventSource().changedTouches)[0];
        } else {
            var coordinates = d3.mouse(this);
        }
        self.tooltip.css('display', useTable);
        self.tooltip.css({
            'left': (self.width/2 - self.margin.left/2) + coordinates[0],
            'top' : (self.height/2 - self.margin.top/2) + coordinates[1]
        });

        if(self.opts.tooltip.customPlaceholder) {
            var tip = self.opts.tooltip.customPlaceholder.replace("%n", d.data.name);
            tip = tip.replace("%v", d.data.value);
            tip = tip.replace("%c", d.data.color);
            self.tooltip.html(tip);
        } else {
            self.tooltip.html("<div style='color: "+d.data.color+";'><b><span style='font-size: 22px;'>■ </span><span style='position:relative; top: -3px; left: -2px;'>"+d.data.name+" :</span></b> <span style='position:relative; top: -3px; left: -2px;'>"+d.data.value+"</span></div>");
        }

        d3.select(this).style('fill', d.data.color);
    });

    g.on(mouseout, function(d, i) {
        var $this = this;
        if(self.is_touch_device) {
            timer = setTimeout(function() {
                self.tooltip.hide();
            }, 3000);

            setTimeout(function() {
                d3.select($this).style('fill', d.data.color);
            }, 3000);
        } else {
            self.tooltip.hide();
            d3.select($this).style('fill', d.data.color);
            d3.select($this).transition().duration(150).attr('transform', 'translate(0, 0)');
        }
    });

    main.exit().remove();

    for(var i in this.legend_hidden) {
        $(i).hide();
    }
};

Rubix.PieDonut.prototype.removePoint = function(ref) {
    var removed = false;
    for(var i=0; i<this.data.length; i++) {
        if(this.data[i].name === ref) {
            removed = true;
            this.data[i].value = 0;
            this.data[i]._remove = true;
            break;
        }
    }

    if(!removed) return;

    this.final_draw('add');
};

Rubix.PieDonut.prototype.updatePoint = function(data) {
    this.addPoint(data);
};

Rubix.PieDonut.prototype.addPoint = function(data) {
    this.data_changed = true;
    var updated = false;
    for(var i=0; i<this.data.length; i++) {
        if(this.data[i].name === data.name) {
            updated = true;
            this.data[i].value = +data.value;
            break;
        }
    }

    if(!updated) {
        this.data.push(data);
    }

    this.final_draw('add');
};

window.Rubix = window.Rubix || {};

/**
 * @param {Rubix} rubix
 * @param {Object} opts
 * @constructor
 */
Rubix.LineSeries = function(rubix, opts) {
    this.opts = opts;
    this.opts.color = this.opts.color || 'steelblue';
    this.opts.marker = this.opts.marker || 'circle';
    this.opts.scatter = this.opts.scatter || false;
    this.opts.noshadow = this.opts.noshadow || false;
    this.show_markers = this.opts.show_markers;
    this.opts.strokewidth = this.opts.strokewidth || 1;

    if(!this.opts.hasOwnProperty('name')) throw new Error('LineSeries should have a \'name\' property');

    this.name = this.opts.name;

    this.chart_hidden = false;
    this.temp_stack = [];

    this.setup = false;
    this._setup(rubix);
};

/**
 * @param {Rubix} rubix
 * @private
 */
Rubix.LineSeries.prototype._setup = function(rubix) {
    this.rubix = rubix;

    this.root   = this.rubix.root;
    this.data   = this.rubix.data;
    this.width  = this.rubix.width;
    this.height = this.rubix.height;
    this.line_series = this.rubix.root_line_series;
    this.show_markers = (this.show_markers === undefined) ? this.rubix.show_markers : this.show_markers;

    this.master_detail = this.rubix.master_detail;

    if(this.master_detail) {
        this.md_root = this.rubix.md_root;
    }

    if(!this.id) {
        this.id = this.rubix.uid('line');
    }

    /** separator */
    var self = this;
    this.line = d3.svg.line();
    this.line.defined(function(d) {
        return d.x !== null && d.y !== null;
    });
    this.line.x(function(d) {
        if(self.rubix.opts.invertAxes) {
            return self.rubix.x(d.y);
        }

        if(self.rubix.axis.x.range === 'column' || self.rubix.axis.x.range === 'bar') {
            return self.rubix.x(d.x) + self.rubix.x.rangeBand()/2;
        }
        return self.rubix.x(d.x);
    });
    this.line.y(function(d) {
        if(self.rubix.opts.invertAxes) {
            if(self.rubix.axis.y.range === 'column' || self.rubix.axis.y.range === 'bar') {
                return self.rubix.y(d.x) + self.rubix.y.rangeBand()/2;
            }
            return self.rubix.y(d.x);
        }
        return self.rubix.y(d.y);
    });
    this.line.interpolate(this.rubix.interpolate);

    if(this.master_detail) {
        this.master_line = d3.svg.line();
        this.master_line.defined(function(d) {
            return d.x !== null && d.y !== null;
        });
        this.master_line.x(function(d) {
            if(self.rubix.opts.invertAxes) {
                return self.rubix.x2(d.y);
            }
            return self.rubix.x2(d.x);
        });
        this.master_line.y(function(d) {
            if(self.rubix.opts.invertAxes) {
                return self.rubix.y2(d.x);
            }
            return self.rubix.y2(d.y);
        });
        this.master_line.interpolate(this.rubix.interpolate);
    }
};

// Alias for draw
Rubix.LineSeries.prototype.redraw = function(rubix) {
    this._setup(rubix);
    this.draw();
};

// Alias for draw
Rubix.LineSeries.prototype.noRedraw = function(rubix) {
    this._setup(rubix);
    this.draw(true);
};

/**
 * @param {Array|Object} data
 */
Rubix.LineSeries.prototype.addData = function(data) {
    this.rubix.data_changed = true;
    if(!(data instanceof Array)) {
        if(!(data instanceof Object)) {
            throw new Error("Data must be an array or object");
        } else {
            if(!(data.hasOwnProperty('x') || data.hasOwnProperty('y'))) {
                throw new Error("Object must be in the form: {x: ..., y: ...}");
            }

            data = [data];
        }
    }

    if(!data.length) return;

    if(!this.rubix.opts.noSort) {
        data.sort(function(a, b) {
            if(a.x > b.x) {
                return 1;
            } else if(a.x === b.x) {
                return 0;
            } else {
                return -1;
            }
        });
    }

    this.data[this.name] = data;

    this.rubix.resetAxis(false);
    this.rubix.forceRedraw();
    this._animate_draw();
};

Rubix.LineSeries.prototype.draw = function(forced) {
    if(!this.name) return;
    if(!this.data) return;
    if(!this.data.hasOwnProperty(this.name)) return;
    if(!this.data[this.name].length) return;

    var self = this;

    var isConstructed = this.line_series.selectAll('.'+this.id)[0].length;

    if(!isConstructed) {
        this.line_series.selectAll('.'+this.id+'.clipped').remove();
        if(!this.opts.scatter) {
            if(!this.opts.noshadow) {
                this.strokePath1 = this.line_series.append('path').attr('class', this.id+' line').datum(this.data[this.name]).attr('d', this.line).attr('stroke', 'black').attr('fill', 'none').attr('stroke-linecap', 'round').attr('stroke-width', 5 * this.opts.strokewidth).attr('stroke-opacity', 0.05000000000000001).attr('transform', 'translate(1,1)');
                this.strokePath2 = this.line_series.append('path').attr('class', this.id+' line').datum(this.data[this.name]).attr('d', this.line).attr('stroke', 'black').attr('fill', 'none').attr('stroke-linecap', 'round').attr('stroke-width', 3 * this.opts.strokewidth).attr('stroke-opacity', 0.1).attr('transform', 'translate(1,1)');
                this.strokePath3 = this.line_series.append('path').attr('class', this.id+' line').datum(this.data[this.name]).attr('d', this.line).attr('stroke', 'black').attr('fill', 'none').attr('stroke-linecap', 'round').attr('stroke-width', 1 * this.opts.strokewidth).attr('stroke-opacity', 0.15000000000000002).attr('transform', 'translate(1,1)');
            }
            this.linePath = this.line_series.append('path').datum(this.data[this.name]).attr('class', this.id+' line').attr('stroke', this.opts.color).attr('fill', 'none').attr('stroke-linecap', 'round').attr('d', this.line).attr('stroke-width', 2 * this.opts.strokewidth);
        }

        if(this.master_detail) {
            if(!this.opts.scatter) {
                this.md_root.select('.md-layers > .md_line_series').selectAll('.'+this.id+'.clipped').remove();
                this.masterLinePath = this.md_root.select('.md-layers > .md_line_series').append('path').datum(this.data[this.name]).attr('class', this.id+' line').attr('stroke', this.opts.color).attr('fill', 'none').attr('stroke-linecap', 'round').attr('d', this.master_line).attr('stroke-width', 2 * this.opts.strokewidth);
                this.masterLinePath.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');
                this.masterLinePath.classed('clipped', true);
            } else {
                this.md_root.select('.md-layers > .md_line_series').attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');
                var self = this;
                var datum = this.data[this.name];
                this.markers_master = this.md_root.select('.md-layers > .md_line_series').selectAll('.'+this.id+'-marker');
                switch(this.opts.marker) {
                    case 'circle':
                    case 'cross':
                    case 'square':
                    case 'diamond':
                    case 'triangle-up':
                    case 'triangle-down':
                        var symbol = d3.svg.symbol();
                        symbol.type(this.opts.marker);

                        var symbolType = this.markers_master.data(datum);
                        symbolType.enter().append('path')
                              .attr('d', symbol)
                              .attr('class', this.id + '-marker')
                              .attr('fill', this.opts.color)
                              .style('display', function(d) {
                                    if(d.y === null) {
                                        return 'none';
                                    }
                                    return null;
                              })
                              .attr('stroke', 'none')
                              .attr('transform', function(d) {
                                    var val = d.y;
                                    if(isNaN(val)) {
                                        val = 0;
                                    }
                                    if(self.rubix.opts.invertAxes) {
                                        var _y = self.rubix.y(d.x);
                                        var _x = self.rubix.x(val);
                                        if(self.rubix.axis.y.range === 'column' || self.rubix.axis.y.range === 'bar') {
                                            _y += self.rubix.y.rangeBand()/2;
                                        }
                                    } else {
                                        var _y = self.rubix.y2(val);
                                        var _x = self.rubix.x2(d.x);
                                        if(self.rubix.axis.x.range === 'column' || self.rubix.axis.x.range === 'bar') {
                                            _x += self.rubix.x.rangeBand()/2;
                                        }
                                    }
                                    return 'translate('+_x+','+_y+')';
                              });
                        symbolType.exit().remove();
                    break;
                    default:
                        throw new Error('Unknown marker type : ' + this.opts.marker);
                    break;
                }
            }

            if(this.dataChanged) {
                this.rubix.resetExtent();
                this.dataChanged = false;
            }

            if(!this.opts.scatter) {
                if(!this.opts.noshadow) {
                    this.strokePath1.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');
                    this.strokePath2.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');
                    this.strokePath3.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');
                }
                this.linePath.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');

                if(!this.opts.noshadow) {
                    this.strokePath1.classed('clipped', true);
                    this.strokePath2.classed('clipped', true);
                    this.strokePath3.classed('clipped', true);
                }
                this.linePath.classed('clipped', true);
            }
        }

        this.setup = true;
    } else {
        this.rubix.runCommand('globalRedraw');
    }

    this.rubix.resetFocus(forced);
};

/**
 * @param {Array|Object} data
 */
Rubix.LineSeries.prototype.update = function(data) {
    if(!(data instanceof Array)) {
        if(!(data instanceof Object)) {
            throw new Error("Data must be an array or object");
        } else {
            if(!(data.hasOwnProperty('x') || data.hasOwnProperty('y'))) {
                throw new Error("Object must be in the form: {x: ..., y: ...}");
            }

            data = [data];
        }
    }

    if(!this.rubix.opts.noSort) {
        data.sort(function(a, b) {
            if(a.x > b.x) {
                return 1;
            } else if(a.x === b.x) {
                return 0;
            } else {
                return -1;
            }
        });
    }

    this.data[this.name] = data;

    if(this.setup) {
        this._animate_draw();
    } else {
        this.rubix.resetAxis(false);
        this.rubix.forceRedraw();
    }
};

/**
 * Alias for addPoint
 * @param {Object} data
 * @param {?Boolean} shift
 * @param {?Boolean} noRedraw
 */
Rubix.LineSeries.prototype.updatePoint = function(data, shift, noRedraw) {
    this.addPoint(data, shift, noRedraw);
};

/**
 * @param {*} ref
 * @param {?Boolean} noRedraw
 */
Rubix.LineSeries.prototype.removePoint = function(ref, noRedraw) {
    if(this.chart_hidden) {
        this.temp_stack.push({
            type: 'removePoint',
            ref: ref
        });
        return;
    }

    if(!this.data[this.name]) {
        this.data[this.name] = [];
    }

    var removed = false;
    for(var i=0; i<this.data[this.name].length; i++) {
        if(this.data[this.name][i].x === ref) {
            this.data[this.name].splice(i, 1);
            removed = true;
            break;
        }
    }

    if(this.master_detail) {
        this.dataChanged = true;
    }

    if(noRedraw) return;

    if(this.setup) {
        this._animate_draw();
    } else {
        this.rubix.resetAxis(true);
        this.rubix.forceRedraw();
    }
};

/**
 * @param {Object} data
 * @param {?Boolean} shift
 * @param {?Boolean} noRedraw
 */
Rubix.LineSeries.prototype.addPoint = function(data, shift, noRedraw) {
    this.rubix.data_changed = true;
    if(!(data instanceof Object) || (data instanceof Array)) {
        throw new Error("Object required for addPoint");
    }
    if(!(data.hasOwnProperty('x') || data.hasOwnProperty('y'))) {
        throw new Error("Object must be in the form: {x: ..., y: ...}");
    }

    if(this.chart_hidden) {
        this.temp_stack.push({
            type: 'addPoint',
            data: data,
            shift: shift
        });
        return;
    }

    if(!this.data[this.name]) {
        this.data[this.name] = [];
    }

    var added = false;
    for(var i=0; i<this.data[this.name].length; i++) {
        if(this.data[this.name][i].x === data.x) {
            this.data[this.name][i].y = data.y;
            added = true;
            break;
        }
    }

    if(!added) {
        this.data[this.name].push(data);
    }

    this.data[this.name].sort(function(a, b) {
        if(a.x > b.x) {
            return 1;
        } else if(a.x === b.x) {
            return 0;
        } else {
            return -1;
        }
    });

    if(this.rubix.opts.interval) {
        if(this.rubix.opts.interval < this.data[this.name].length) {
            this.data[this.name].shift();
        }
    } else {
        if(shift) {
            this.data[this.name].shift();
        }
    }

    if(this.master_detail) {
        this.dataChanged = true;
    }

    if(noRedraw) return;
    if(this.setup) {
        this._animate_draw();
    } else {
        this.rubix.resetAxis(true);
        this.rubix.forceRedraw();
    }
};

Rubix.LineSeries.prototype._animate_draw = function() {
    var text = this.root.selectAll('.y.axis').selectAll('text')[0];
    var width = [];
    for(var i=0; i<text.length; i++) {
        width.push(text[i].getBBox().width);
    }
    var origMaxWidth = d3.max(width);

    this.rubix.resetAxis(true);

    this.rubix.runCommand('globalRedraw');

    text = this.root.selectAll('.y.axis').selectAll('text')[0];
    width = [];
    for(var i=0; i<text.length; i++) {
        width.push(text[i].getBBox().width);
    }

    var maxWidth = d3.max(width);

    this.rubix.marginLeft(maxWidth);
    this.rubix.resetFocus();
};

Rubix.LineSeries.prototype.hidden = function() {
    this.chart_hidden = true;
};

Rubix.LineSeries.prototype.show = function() {
    this.chart_hidden = false;
    while(this.temp_stack.length>1) {
        var rawData = this.temp_stack.shift();
        if(rawData.type === 'addPoint') {
            this.addPoint(rawData.data, rawData.shift, true);
        } else {
            this.removePoint(rawData.ref);
        }
    }
    if(this.temp_stack.length) {
        var rawData = this.temp_stack.shift();
        if(rawData.type === 'addPoint') {
            this.addPoint(rawData.data, rawData.shift, true);
        } else {
            this.removePoint(rawData.ref);
        }
    }
};

Rubix.LineSeries.prototype.globalRedraw = function(rubix) {
    this.line_series.selectAll('.'+this.id+'.line').attr('d', this.line);
};

Rubix.LineSeries.prototype.forceRedraw = function(rubix) {
    this.redraw(rubix);
};

Rubix.LineSeries.prototype.on_complete_draw = function() {
    if(!this.setup) return;
    var self = this;
    var datum = this.data[this.name];
    if(this.opts.marker !== undefined && this.show_markers) {
        if(this.master_detail) {
            this.rubix.symbols.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip-symbols)');
        }
        this.markers = this.rubix.symbols.selectAll('.'+this.id+'-marker');
        switch(this.opts.marker) {
            case 'circle':
            case 'cross':
            case 'square':
            case 'diamond':
            case 'triangle-up':
            case 'triangle-down':
                var symbol = d3.svg.symbol();
                symbol.type(this.opts.marker);

                var symbolType = this.markers.data(datum);
                symbolType.enter().append('path')
                      .attr('d', symbol)
                      .attr('class', this.id + '-marker')
                      .attr('fill', this.opts.color)
                      .style('display', function(d) {
                            if(d.y === null) {
                                return 'none';
                            }
                            return null;
                      })
                      .attr('stroke', 'white')
                      .attr('transform', function(d) {
                            var val = d.y;
                            if(isNaN(val)) {
                                val = 0;
                            }
                            if(self.rubix.opts.invertAxes) {
                                var _y = self.rubix.y(d.x);
                                var _x = self.rubix.x(val);
                                if(self.rubix.axis.y.range === 'column' || self.rubix.axis.y.range === 'bar') {
                                    _y += self.rubix.y.rangeBand()/2;
                                }
                            } else {
                                var _y = self.rubix.y(val);
                                var _x = self.rubix.x(d.x);
                                if(self.rubix.axis.x.range === 'column' || self.rubix.axis.x.range === 'bar') {
                                    _x += self.rubix.x.rangeBand()/2;
                                }
                            }
                            return 'translate('+_x+','+_y+')';
                      });
                symbolType.exit().remove();
            break;
            default:
                throw new Error('Unknown marker type : ' + this.opts.marker);
            break;
        }
    }
};

Rubix.LineSeries.prototype.setupFocus = function() {
    if(!this.setup) return;
    if(this.focus) this.focus.remove();
    this.focus = this.rubix.focus_group.append('g');
    this.focus.attr('class', 'focus');
    this.focus.style('display', 'none');
    switch(this.opts.marker) {
        case 'circle':
        case 'cross':
        case 'square':
        case 'diamond':
        case 'triangle-up':
        case 'triangle-down':
            var symbol = d3.svg.symbol();
            symbol.type(this.opts.marker);
            var path = this.focus.append('path').attr('d', symbol).attr('fill', this.opts.color).attr('stroke', 'white').attr('stroke-width', 2);
        break;
        default:
            throw new Error('Unknown marker type : ' + this.opts.marker);
        break;
    }
};

Rubix.LineSeries.prototype.on_focus = function() {
    if(!this.setup) return;
    this.linePath.attr('stroke-width', 2 * this.opts.strokewidth);
};

Rubix.LineSeries.prototype.off_focus = function() {
    if(!this.setup) return;
    this.linePath.attr('stroke-width', 2 * this.opts.strokewidth);
};
window.Rubix = window.Rubix || {};

/**
 * @param {Rubix} rubix
 * @param {Object} opts
 * @constructor
 */
Rubix.ColumnSeries = function(rubix, opts) {
    this.type = 'column_series';
    this.opts = opts;
    this.opts.color = this.opts.color || 'steelblue';
    this.opts.marker = this.opts.marker || 'circle';
    this.opts.nostroke = this.opts.nostroke || '';
    this.opts.fillopacity = this.opts.fillopacity || 0.85;
    this.show_markers = this.opts.show_markers;

    if(!this.opts.hasOwnProperty('name')) throw new Error('ColumnSeries should have a \'name\' property');

    this.name = this.opts.name;

    this.chart_hidden = false;
    this.temp_stack = [];

    this.setup = false;
    this.last_stack = [];
    this.last_stack_name = '';
    this.last_stack_added = false;

    this.count = 0;

    this._setup(rubix);
};

/**
 * @param {Rubix} rubix
 * @private
 */
Rubix.ColumnSeries.prototype._setup = function(rubix) {
    this.rubix = rubix;
    this.rubix.stacked = true;

    this.root   = this.rubix.root;
    this.data   = this.rubix.data;
    this.width  = this.rubix.width;
    this.height = this.rubix.height;
    this.stack  = this.rubix.column_stack;
    this.offset = this.rubix.column_offset;
    this.cb_series = this.rubix.root_cb_series;
    this.column_stack = this.rubix.column_stack_data;
    this.show_markers = (this.show_markers === undefined) ? this.rubix.show_markers : this.show_markers;

    this.grouped = this.rubix.grouped;

    if(!this.id) {
        this.id = this.rubix.uid('stacked-column');
    }
};

// Alias for draw
Rubix.ColumnSeries.prototype.redraw = function(rubix) {
    this._setup(rubix);
    this.draw();
};

Rubix.ColumnSeries.prototype.noRedraw = function(rubix) {
    this._setup(rubix);
    this.draw(true);
};

/**
 * @param {Array|Object} data
 */
Rubix.ColumnSeries.prototype.addData = function(data) {
    this.rubix.data_changed = true;
    if(!(data instanceof Array)) {
        if(!(data instanceof Object)) {
            throw new Error("Data must be an array or object");
        } else {
            if(!(data.hasOwnProperty('x') || data.hasOwnProperty('y'))) {
                throw new Error("Object must be in the form: {x: ..., y: ...}");
            }

            data = [data];
        }
    }

    if(!data.length) return;

    if(!this.rubix.opts.noSort) {
        data.sort(function(a, b) {
            if(a.x > b.x) {
                return 1;
            } else if(a.x === b.x) {
                return 0;
            } else {
                return -1;
            }
        });
    }

    var maxLen = 0, columns = [];
    for(var i in this.data) {
        var len = this.data[i].length;
        if(len > maxLen) {
            maxLen = len;
            columns = [];
            for(var k=0; k<maxLen; k++) {
                columns.push(this.data[i][k].x);
            }
        }
    }

    this.rubix.maxLen = maxLen;

    this.data[this.name] = data;

    for(var i in this.data) {
        var len = this.data[i].length;
        if(len < maxLen) {
            var dup_columns = columns.concat();
            for(var j=0; j<this.data[i].length; j++) {
                var column_index = dup_columns.indexOf(this.data[i][j].x);
                dup_columns.splice(column_index, 1);
            }
            for(var j=0; j<dup_columns.length; j++) {
                this.rubix.charts[i].addPoint({
                    x: dup_columns[j],
                    y: null
                });
            }
        }
    }

    this.column_stack.push({
        key: this.name,
        color: this.opts.color,
        marker: this.opts.marker,
        values: this.data[this.name],
        orderKey: this.column_stack.length
    });

    this.rubix.resetAxis();
    this.rubix.forceRedraw();
    this._animate_draw();
};

Rubix.ColumnSeries.prototype._createRect = function() {
    var self = this;
    var strokecolor = 'white';
    if(this.opts.nostroke) strokecolor = 'none';
    var rect = this.columnGroup.selectAll('rect').data(function(d) { return d.values; }).enter().append('rect').attr('stroke', strokecolor);
    if(this.grouped) {
        if(this.rubix.opts.invertAxes) {
            rect.attr('x', function(d, i, j) {
                var val = self.rubix.x(0);
                if(isNaN(val)) {
                    return null;
                }
                return val;
            });
            rect.attr('y', function(d, i, j) {
                return self.rubix.y(d.x) + self.rubix.y.rangeBand() / self.layers.length * j;
            });
            rect.attr('class', function(d, i, j) {
                return 'column-' + ((self.rubix.y(d.x) + (self.rubix.y.rangeBand()/self.layers.length) * (self.layers.length-1)) + self.rubix.y.rangeBand()/(2*self.layers.length));
            });
            rect.attr("width", function(d) {
                var val = Math.abs(self.rubix.x(d.y) - self.rubix.x(0));
                if(isNaN(val)) {
                    return null;
                }
                return val;
            });
            rect.attr('height', self.rubix.y.rangeBand() / self.layers.length);
            rect.attr('transform', function(d) {
                var val = self.rubix.x(d.y_new) - self.rubix.x(0);
                if(d.y < 0) {
                    return 'translate('+(-Math.abs(val))+',0)';
                } else {
                    return 'translate(0,0)';
                }
            });
        } else {
            rect.attr('x', function(d, i, j) {
                return self.rubix.x(d.x) + self.rubix.x.rangeBand() / self.layers.length * j;
            });
            rect.attr('y', function(d) {
                var val = self.rubix.y(0);
                if(isNaN(val)) {
                    return null;
                }
                return val;
            });
            rect.attr('class', function(d, i, j) {
                return 'column-' + ((self.rubix.x(d.x) + (self.rubix.x.rangeBand()/self.layers.length) * (self.layers.length-1)) + self.rubix.x.rangeBand()/(2*self.layers.length));
            });
            rect.attr("width", self.rubix.x.rangeBand() / self.layers.length)
            rect.attr('height', function(d) {
                var val = Math.abs(self.rubix.y(d.y) - self.rubix.y(0));
                if(isNaN(val)) {
                    return null;
                }
                return val;
            });
            rect.attr('transform', function(d) {
                var val = self.rubix.y(d.y_new) - self.rubix.y(0);
                if(d.y > 0) {
                    return 'translate(0,'+(-Math.abs(val))+')';
                } else {
                    return 'translate(0,0)';
                }
            });
        }
    } else {
        if(this.rubix.opts.invertAxes) {
            rect.attr('x', function(d) {
                var val = self.rubix.x(d.y0);
                if(isNaN(val)) {
                    return null;
                }
                return val;
            });
            rect.attr('y', function(d) {
                return self.rubix.y(d.x) || 0;
            });
            rect.attr('class', function(d) {
                return 'column-' + (self.rubix.y(d.x) + self.rubix.y.rangeBand()/2);
            });
            rect.attr("width", function(d) {
                var val = Math.abs(self.rubix.x(d.y0)-self.rubix.x(d.y+d.y0));
                if(isNaN(val)) {
                    return null;
                }
                return val;
            });
            rect.attr('height', self.rubix.y.rangeBand());
            rect.attr('transform', function(d) {
                if(d.y < 0) {
                    var val = Math.abs(self.rubix.x(d.y0)-self.rubix.x(d.y+d.y0));
                    return 'translate('+(-val)+',0)';
                }
                return null;
            });
        } else {
            rect.attr('x', function(d) {
                return self.rubix.x(d.x);
            });
            rect.attr('y', function(d) {
                var val = self.rubix.y(d.y0);
                if(isNaN(val)) {
                    return null;
                }
                return val;
            });
            rect.attr('class', function(d) {
                return 'column-' + ((self.rubix.x(d.x) + self.rubix.x.rangeBand()/2));
            });
            rect.attr("width", self.rubix.x.rangeBand())
            rect.attr('height', function(d) {
                var val = Math.abs(self.rubix.y(d.y0)-self.rubix.y(d.y+d.y0));
                if(isNaN(val)) {
                    return null;
                }
                return val;
            });
            rect.attr('transform', function(d) {
                if(d.y > 0) {
                    var val = Math.abs(self.rubix.y(d.y0)-self.rubix.y(d.y+d.y0));
                    return 'translate(0,'+(-val)+')';
                }
                return null;
            });
        }
    }
};

Rubix.ColumnSeries.prototype.draw = function(forced) {
    try {
        if(!this.name) return;
        if(!this.data) return;
        if(!this.data.hasOwnProperty(this.name)) return;
        if(!this.data[this.name].length) return;

        var oldLayers = this.layers;

        try {
            this.layers = this.stack(this.column_stack);
        } catch(e) {
            // data un-available. retaining old layer.
            this.layers = oldLayers;
        }

        if(!this.grouped) {
            var max = this.rubix.maxLen;
            for(var i=0; i<max; i++) {
                var x = this.layers[0].values[i].x;
                var ceiling = null;
                for(var j=0; j<this.layers.length; j++) {
                    for(var k=0; k<max; k++) {
                        try {
                            if(this.layers[j].values[k].x === x) {
                                if(this.layers[j].values[k].y >= 0) {
                                    if(this.layers[j].values[k].y === null) {
                                        this.layers[j].values[k].y0 = null;
                                    } else {
                                        if(ceiling === null) ceiling = 0;
                                        this.layers[j].values[k].y0 = ceiling;
                                        ceiling += this.layers[j].values[k].y;
                                    }
                                }
                                break;
                            }
                        } catch(e) {
                            this.layers[j].values.push({
                                x: x,
                                y: null,
                                y0: null,
                                y_new: null
                            });
                        }
                    }
                }
                ceiling = null;
                for(var j=this.layers.length-1; j>=0; j--) {
                    for(var k=0; k<max; k++) {
                        try {
                            if(this.layers[j].values[k].x === x) {
                                if(this.layers[j].values[k].y < 0) {
                                    if(this.layers[j].values[k].y === null) {
                                        this.layers[j].values[k].y0 = null;
                                    } else {
                                        if(ceiling === null) ceiling = 0;
                                        this.layers[j].values[k].y0 = -ceiling;
                                        ceiling += Math.abs(this.layers[j].values[k].y);
                                    }
                                }
                                break;
                            }
                        } catch(e) {
                            this.layers[j].values.push({
                                x: x,
                                y: null,
                                y0: null,
                                y_new: null
                            });
                        }
                    }
                }
            }
        }

        var self = this;
        var isConstructed = this.cb_series.selectAll('.'+this.id)[0].length;
        if(!isConstructed) {
            try {
                this.cb_series.selectAll('.column-layer').remove();
                var p = this.cb_series.selectAll('.column-layer').data(this.layers, function(d, i) {
                    if(d.key === self.name) {
                        self.count = i;
                    }
                    return d.key;
                });

                this.columnGroup = p.enter().append('g');
                this.columnGroup.attr('class', 'column-layer')
                           .attr('fill', function(d) { return d.color; }).attr('fill-opacity', this.opts.fillopacity);

                this._createRect();

                p.exit().remove();
            } catch(e) {
                // do nothing
            }
            this.setup = true;
        } else {
            this.rubix.runCommand('globalRedraw');
        }

        this.rubix.resetFocus(forced);
    } catch(e) {
        // do nothing
    }
};

/**
 * @param {Array|Object} data
 */
Rubix.ColumnSeries.prototype.update = function(data) {
    if(!(data instanceof Array)) {
        if(!(data instanceof Object)) {
            throw new Error("Data must be an array or object");
        } else {
            if(!(data.hasOwnProperty('x') || data.hasOwnProperty('y'))) {
                throw new Error("Object must be in the form: {x: ..., y: ...}");
            }

            data = [data];
        }
    }

    if(!this.rubix.opts.noSort) {
        data.sort(function(a, b) {
            if(a.x > b.x) {
                return 1;
            } else if(a.x === b.x) {
                return 0;
            } else {
                return -1;
            }
        });
    }

    this.data[this.name] = data;

    if(this.setup) {
        this._animate_draw();
    } else {
        this.rubix.resetAxis(true);
        this.rubix.forceRedraw();
    }
};

/**
 * @param {Object} data
 * @param {?Boolean} shift
 * @param {?Boolean} noRedraw
 */
Rubix.ColumnSeries.prototype.updatePoint = function(data, shift, noRedraw) {
    this.addPoint(data, shift, noRedraw);
};

/**
 * @param {*} ref
 * @param {?Boolean} noRedraw
 */
Rubix.ColumnSeries.prototype.removePoint = function(ref, noRedraw) {
    if(this.chart_hidden) {
        this.temp_stack.push({
            type: 'removePoint',
            ref: ref
        });
        return;
    }

    if(!this.data[this.name]) {
        this.data[this.name] = [];
    }

    var removed = false, pos = 0;
    for(var i=0; i<this.data[this.name].length; i++) {
        if(this.data[this.name][i].x === ref) {
            this.data[this.name][i].y = null;
            this.data[this.name][i].y0 = null;
            this.data[this.name][i].y_new = null;
            pos = i;
            removed = true;
            break;
        }
    }

    if(!removed) return;

    var found = false;
    for(var i=0; i<this.column_stack.length; i++) {
        var st = this.column_stack[i];
        try {
            if(st.values[pos].x === ref) {
                if(st.values[pos].y !== null) {
                    found = true;
                    break;
                }
            }
        } catch(e) {
            // do nothing
        }
    }

    if(!found) {
        for(var i=0; i<this.column_stack.length; i++) {
            this.column_stack[i].values.splice(pos, 1);
        }
    }

    var maxLen = 0;
    for(var i in this.data) {
        var len = this.data[i].length;
        if(len > maxLen) {
            maxLen = len;
        }
    }

    this.rubix.maxLen = maxLen;

    if(noRedraw) return;

    this.rubix.resetAxis();
    this.rubix.forceRedraw();
    this._animate_draw();
};

/**
 * @param {Object} data
 * @param {?Boolean} shift
 * @param {?Boolean} noRedraw
 */
Rubix.ColumnSeries.prototype.addPoint = function(data, shift, noRedraw) {
    this.rubix.data_changed = true;
    if(!(data instanceof Object) || (data instanceof Array)) {
        throw new Error("Object required for addPoint");
    }
    if(!(data.hasOwnProperty('x') || data.hasOwnProperty('y'))) {
        throw new Error("Object must be in the form: {x: ..., y: ...}");
    }

    if(this.chart_hidden) {
        this.temp_stack.push({
            type: 'addPoint',
            data: data,
            shift: shift
        });
        return;
    }

    if(!this.data[this.name]) {
        this.data[this.name] = [];
    }

    var added = false;
    for(var i=0; i<this.data[this.name].length; i++) {
        if(this.data[this.name][i].x === data.x) {
            this.data[this.name][i].y = data.y;
            added = true;
            break;
        }
    }

    if(!added) {
        this.data[this.name].push(data);
    }

    this.data[this.name].sort(function(a, b) {
        if(a.x > b.x) {
            return 1;
        } else if(a.x === b.x) {
            return 0;
        } else {
            return -1;
        }
    });

    added = false;
    for(var i=0; i<this.column_stack.length; i++) {
        if(this.column_stack[i].key === this.name) {
            added = true;
            break;
        }
    }

    if(this.rubix.opts.interval) {
        if(this.rubix.opts.interval < this.data[this.name].length) {
            this.data[this.name].shift();
        }
    } else {
        if(shift) {
            this.data[this.name].shift();
        }
    }

    var maxLen = 0, columns = [];
    for(var i in this.data) {
        var len = this.data[i].length;
        if(len > maxLen) {
            maxLen = len;
            columns = [];
            for(var k=0; k<maxLen; k++) {
                columns.push(this.data[i][k].x);
            }
        }
    }

    this.rubix.maxLen = maxLen;

    for(var i in this.data) {
        var len = this.data[i].length;
        if(len < maxLen) {
            var dup_columns = columns.concat();
            for(var j=0; j<this.data[i].length; j++) {
                var column_index = dup_columns.indexOf(this.data[i][j].x);
                dup_columns.splice(column_index, 1);
            }
            for(var j=0; j<dup_columns.length; j++) {
                this.rubix.charts[i].addPoint({
                    x: dup_columns[j],
                    y: null
                });
            }
        }
    }

    if(noRedraw) return;

    this.rubix.resetAxis();
    this.rubix.forceRedraw();
    this._animate_draw();
};

Rubix.ColumnSeries.prototype._animate_draw = function() {
    try {
        var text = this.root.selectAll('.y.axis').selectAll('text')[0];
        var width = [];
        for(var i=0; i<text.length; i++) {
            width.push(text[i].getBBox().width);
        }
        var origMaxWidth = d3.max(width);

        this.rubix.resetAxis(true);

        this.rubix.runCommand('globalRedraw');

        text = this.root.selectAll('.y.axis').selectAll('text')[0];
        width = [];
        for(var i=0; i<text.length; i++) {
            width.push(text[i].getBBox().width);
        }

        var maxWidth = d3.max(width);

        this.rubix.marginLeft(maxWidth);
        this.rubix.resetFocus();
    } catch(e) {
        // do nothing
    }
};

Rubix.ColumnSeries.prototype.hidden = function() {
    this.chart_hidden = true;
};

Rubix.ColumnSeries.prototype.show = function() {
    this.chart_hidden = false;
    while(this.temp_stack.length>1) {
        var rawData = this.temp_stack.shift();
        if(rawData.type === 'addPoint') {
            this.addPoint(rawData.data, rawData.shift, true);
        } else {
            this.removePoint(rawData.ref);
        }
    }
    if(this.temp_stack.length) {
        var rawData = this.temp_stack.shift();
        if(rawData.type === 'addPoint') {
            this.addPoint(rawData.data, rawData.shift, true);
        } else {
            this.removePoint(rawData.ref);
        }
    }
};


Rubix.ColumnSeries.prototype.globalRedraw = function(rubix) {
    // do nothing
};

Rubix.ColumnSeries.prototype.forceRedraw = function(rubix) {
    this.redraw(rubix);
};

Rubix.ColumnSeries.prototype.on_complete_draw = function() {
    if(!this.setup) return;
    var strokecolor = 'white';
    var self = this;
    if(this.opts.marker !== undefined && this.show_markers) {
        this.markers = this.rubix.symbols;

        this.markers.selectAll('.column-symbols').remove();
        var p = this.markers.selectAll('.column-symbols').data(this.layers, function(d) {
            return d.key;
        });

        var symbolGroup = p.enter().append('g');
        symbolGroup.attr('class', 'column-symbols');

        switch(this.opts.marker) {
            case 'circle':
            case 'cross':
            case 'square':
            case 'diamond':
            case 'triangle-up':
            case 'triangle-down':
                var symbolPath = symbolGroup.selectAll('path').data(function(d) {
                    for(var i=0; i<d.values.length; i++) {
                        d.values[i].marker = d.marker;
                        d.values[i].color = d.color;
                    }
                    return d.values;
                }).enter().append('path');
                symbolPath
                      .attr('d', function(d) {
                            var symbol = d3.svg.symbol();
                            symbol.type(d.marker);
                            return symbol();
                      })
                      .attr('fill', function(d) {
                            return d.color;
                      })
                      .style('display', function(d) {
                            if(self.rubix.column_offset === 'expand') {
                                if(d.y_new === 0 && d.y0 === 1) {
                                    return 'none';
                                }
                                return null;
                            }
                            if(d.y_new === null) {
                                return 'none';
                            }
                            return null;
                      })
                      .attr('stroke', strokecolor)
                      if(this.rubix.opts.invertAxes) {
                          symbolPath.attr('transform', function(d, i, j) {
                                var val = d.y0 + d.y_new;
                                if(isNaN(val)) {
                                    val = 0;
                                }
                                var _y = self.rubix.x(val);
                                if(self.grouped) {
                                    _y = self.rubix.x(d.y_new);
                                }
                                var _x = self.rubix.y(d.x) + self.rubix.y.rangeBand()/2;
                                if(self.grouped) {
                                    _x = (self.rubix.y(d.x) + (self.rubix.y.rangeBand()/self.layers.length) * j) + self.rubix.y.rangeBand()/(2*self.layers.length);
                                }
                                return 'translate('+_y+','+_x+')';
                          });
                      } else {
                          symbolPath.attr('transform', function(d, i, j) {
                                var val = d.y0 + d.y_new;
                                if(isNaN(val)) {
                                    val = 0;
                                }
                                var _y = self.rubix.y(val);
                                if(self.grouped) {
                                    _y = self.rubix.y(d.y_new);
                                }
                                var _x = self.rubix.x(d.x) + self.rubix.x.rangeBand()/2;
                                if(self.grouped) {
                                    _x = (self.rubix.x(d.x) + (self.rubix.x.rangeBand()/self.layers.length) * j) + self.rubix.x.rangeBand()/(2*self.layers.length);
                                }
                                return 'translate('+_x+','+_y+')';
                          });
                      }
                p.exit().remove();
            break;
            default:
                throw new Error('Unknown marker type : ' + this.opts.marker);
            break;
        }
    }
};

Rubix.ColumnSeries.prototype.setupFocus = function() {
    if(!this.setup) return;
    if(this.focus) this.focus.remove();
    this.focus = this.rubix.focus_group.append('g');
    this.focus.attr('class', 'focus');
    this.focus.style('display', 'none');
    var strokecolor = 'white';
    switch(this.opts.marker) {
        case 'circle':
        case 'cross':
        case 'square':
        case 'diamond':
        case 'triangle-up':
        case 'triangle-down':
            var symbol = d3.svg.symbol();
            symbol.type(this.opts.marker);
            this.focus.append('path').attr('d', symbol).attr('fill', this.opts.color).attr('stroke', strokecolor).attr('stroke-width', 2);
        break;
        default:
            throw new Error('Unknown marker type : ' + this.opts.marker);
        break;
    }
};

Rubix.ColumnSeries.prototype.on_focus = function(dx, dy) {
    if(!this.setup) return;
    this.off_focus();
    if(this.rubix.opts.invertAxes) {
        dy = dy.toString().replace('.', '\\\.');
        var rects = this.cb_series.selectAll('.column-'+dy);
    } else {
        dx = dx.toString().replace('.', '\\\.');
        var rects = this.cb_series.selectAll('.column-'+dx);
    }
    rects.classed('filled', true);
    rects.attr('fill-opacity', 1);
    rects.attr('stroke-width', 2);
};

Rubix.ColumnSeries.prototype.off_focus = function() {
    if(!this.setup) return;
    var rects = this.cb_series.selectAll('.filled');
    rects.classed('filled', false);
    rects.attr('fill-opacity', 0.85);
    rects.attr('stroke-width', 1);
};

window.Rubix = window.Rubix || {};

/**
 * @param {Rubix} rubix
 * @param {Object} opts
 * @constructor
 */
Rubix.AreaSeries = function(rubix, opts) {
    this.opts = opts;
    this.opts.color = this.opts.color || 'steelblue';
    this.opts.marker = this.opts.marker || 'circle';
    this.opts.fillopacity = this.opts.fillopacity || 0.5;
    this.opts.strokewidth = this.opts.strokewidth || 1;
    this.opts.noshadow = this.opts.noshadow || false;
    this.show_markers = this.opts.show_markers;

    if(!this.opts.hasOwnProperty('name')) throw new Error('AreaSeries should have a \'name\' property');

    this.name = this.opts.name;

    this.chart_hidden = false;
    this.temp_stack = [];

    this.setup = false;
    this.animating = false;

    this._setup(rubix);
};

/**
 * @param {Rubix} rubix
 * @private
 */
Rubix.AreaSeries.prototype._setup = function(rubix) {
    this.rubix = rubix;

    this.root   = this.rubix.root;
    this.data   = this.rubix.data;
    this.width  = this.rubix.width;
    this.height = this.rubix.height;
    this.area_series = this.rubix.root_area_series;
    this.show_markers = (this.show_markers === undefined) ? this.rubix.show_markers : this.show_markers;

    this.master_detail = this.rubix.master_detail;

    if(this.master_detail) {
        this.md_root = this.rubix.md_root;
    }

    if(!this.id) {
        this.id = this.rubix.uid('area');
    }

    /** separator */
    var self = this;
    this.line = d3.svg.line();
    this.line.defined(function(d) {
        return d.x !== null && d.y !== null;
    });
    this.line.x(function(d) {
        if(self.rubix.opts.invertAxes) {
            return self.rubix.x(d.y);
        }
        if(self.rubix.axis.x.range === 'column' || self.rubix.axis.x.range === 'bar') {
            return self.rubix.x(d.x) + self.rubix.x.rangeBand()/2;
        }
        return self.rubix.x(d.x);
    });
    this.line.y(function(d) {
        if(self.rubix.opts.invertAxes) {
            if(self.rubix.axis.y.range === 'column' || self.rubix.axis.y.range === 'bar') {
                return self.rubix.y(d.x) + self.rubix.y.rangeBand()/2;
            }
            return self.rubix.y(d.x);
        }
        return self.rubix.y(d.y);
    });
    this.line.interpolate(this.rubix.interpolate);

    this.area = d3.svg.area();
    this.area.defined(this.line.defined());
    if(this.rubix.opts.invertAxes) {
        this.area.x0(function(d) {
            return self.rubix.x(0);
        });
        this.area.x1(function(d) {
            return self.rubix.x(d.y);
        });
        this.area.y(function(d) {
            if(self.rubix.axis.y.range === 'column' || self.rubix.axis.y.range === 'bar') {
                return self.rubix.y(d.x) + self.rubix.y.rangeBand()/2;
            }
            return self.rubix.y(d.x);
        });
        this.area.interpolate(this.rubix.interpolate);
    } else {
        this.area.x(function(d) {
            if(self.rubix.axis.x.range === 'column' || self.rubix.axis.x.range === 'bar') {
                return self.rubix.x(d.x) + self.rubix.x.rangeBand()/2;
            }
            return self.rubix.x(d.x);
        });
        this.area.y0(function(d) {
            return self.rubix.y(0);
        });
        this.area.y1(function(d) {
            return self.rubix.y(d.y);
        });
        this.area.interpolate(this.rubix.interpolate);
    }

    if(this.master_detail) {
        this.master_line = d3.svg.line();
        this.master_line.defined(function(d) {
            return d.x !== null && d.y !== null;
        });
        this.master_line.x(function(d) {
            if(self.rubix.opts.invertAxes) {
                return self.rubix.x2(d.y);
            }
            return self.rubix.x2(d.x);
        });
        this.master_line.y(function(d) {
            if(self.rubix.opts.invertAxes) {
                return self.rubix.y2(d.x);
            }
            return self.rubix.y2(d.y);
        });
        this.master_line.interpolate(this.rubix.interpolate);

        this.master_area = d3.svg.area();
        this.master_area.defined(this.master_line.defined());
        this.area.interpolate(this.rubix.interpolate);
        if(this.rubix.opts.invertAxes) {
            this.master_area.x0(function(d) {
                return self.rubix.x2(0);
            });
            this.master_area.x1(function(d) {
                return self.rubix.x2(d.y);
            });
            this.master_area.y(function(d) {
                return self.rubix.y2(d.x);
            });
            this.master_area.interpolate(this.rubix.interpolate);
        } else {
            this.master_area.x(function(d) {
                return self.rubix.x2(d.x);
            });
            this.master_area.y0(function(d) {
                return self.rubix.y2(0);
            });
            this.master_area.y1(function(d) {
                return self.rubix.y2(d.y);
            });
            this.master_area.interpolate(this.rubix.interpolate);
        }
    }
};

// Alias for draw
Rubix.AreaSeries.prototype.redraw = function(rubix) {
    this._setup(rubix);
    this.draw();
};

Rubix.AreaSeries.prototype.noRedraw = function(rubix) {
    this._setup(rubix);
    this.draw(true);
};

/**
 * @param {Array|Object} data
 */
Rubix.AreaSeries.prototype.addData = function(data) {
    this.rubix.data_changed = true;
    if(!(data instanceof Array)) {
        if(!(data instanceof Object)) {
            throw new Error("Data must be an array or object");
        } else {
            if(!(data.hasOwnProperty('x') || data.hasOwnProperty('y'))) {
                throw new Error("Object must be in the form: {x: ..., y: ...}");
            }

            data = [data];
        }
    }

    if(!data.length) return;

    if(!this.rubix.opts.noSort) {
        data.sort(function(a, b) {
            if(a.x > b.x) {
                return 1;
            } else if(a.x === b.x) {
                return 0;
            } else {
                return -1;
            }
        });
    }

    this.data[this.name] = data;

    this.rubix.resetAxis(false);
    this.rubix.forceRedraw();
    this._animate_draw();
};

Rubix.AreaSeries.prototype.draw = function(forced) {
    if(!this.name) return;
    if(!this.data) return;
    if(!this.data.hasOwnProperty(this.name)) return;
    if(!this.data[this.name].length) return;
    var self = this;

    var isConstructed = this.area_series.selectAll('.'+this.id)[0].length;

    if(!isConstructed) {
        this.area_series.selectAll('.'+this.id+'.clipped').remove();
        if(!this.opts.noshadow) {
            this.strokePath1 = this.area_series.append('path').attr('class', this.id+' line').datum(this.data[this.name]).attr('d', this.line).attr('stroke', 'black').attr('fill', 'none').attr('stroke-linecap', 'round').attr('stroke-width', 5 * this.opts.strokewidth).attr('stroke-opacity', 0.05000000000000001).attr('transform', 'translate(1,1)');
            this.strokePath2 = this.area_series.append('path').attr('class', this.id+' line').datum(this.data[this.name]).attr('d', this.line).attr('stroke', 'black').attr('fill', 'none').attr('stroke-linecap', 'round').attr('stroke-width', 3 * this.opts.strokewidth).attr('stroke-opacity', 0.1).attr('transform', 'translate(1,1)');
            this.strokePath3 = this.area_series.append('path').attr('class', this.id+' line').datum(this.data[this.name]).attr('d', this.line).attr('stroke', 'black').attr('fill', 'none').attr('stroke-linecap', 'round').attr('stroke-width', 1 * this.opts.strokewidth).attr('stroke-opacity', 0.15000000000000002).attr('transform', 'translate(1,1)');
        }
        this.areaPath = this.area_series.append('path').attr('class', this.id+' area').datum(this.data[this.name]).attr('d', this.area).attr('fill', this.opts.color).attr('fill-opacity', this.opts.fillopacity).attr('stroke', 'none');
        this.linePath = this.area_series.append('path').datum(this.data[this.name]).attr('class', this.id+' line').attr('stroke', this.opts.color).attr('fill', 'none').attr('stroke-linecap', 'round').attr('d', this.line).attr('stroke-width', 2 * this.opts.strokewidth);

        if(this.master_detail) {
            this.md_root.select('.md-layers > .md_area_series').selectAll('.'+this.id+'.clipped').remove();
            this.masterLinePath = this.md_root.select('.md-layers > .md_area_series').append('path').datum(this.data[this.name]).attr('class', this.id+' line').attr('stroke', this.opts.color).attr('fill', 'none').attr('stroke-linecap', 'round').attr('d', this.master_line).attr('stroke-width', 2 * this.opts.strokewidth);
            this.masterLinePath.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');
            this.masterLinePath.classed('clipped', true);

            this.masterAreaPath = this.md_root.select('.md-layers > .md_area_series').append('path').attr('class', this.id+' area').datum(this.data[this.name]).attr('d', this.master_area).attr('fill', this.opts.color).attr('fill-opacity', this.opts.fillopacity).attr('stroke', 'none');
            this.masterAreaPath.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');
            this.masterAreaPath.classed('clipped', true);

            if(this.dataChanged) {
                this.rubix.resetExtent();
                this.dataChanged = false;
            }

            if(!this.opts.noshadow) {
                this.strokePath1.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');
                this.strokePath2.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');
                this.strokePath3.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');
            }
            this.linePath.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');

            if(!this.opts.noshadow) {
                this.strokePath1.classed('clipped', true);
                this.strokePath2.classed('clipped', true);
                this.strokePath3.classed('clipped', true);
            }
            this.linePath.classed('clipped', true);
            this.areaPath.classed('clipped', true);
        }
        this.areaPath.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip)');

        this.setup = true;
    } else {
        this.rubix.runCommand('globalRedraw');
    }

    this.rubix.resetFocus(forced);
};

/**
 * Alias for addPoint
 * @param {Object} data
 * @param {?Boolean} shift
 * @param {?Boolean} noRedraw
 */
Rubix.AreaSeries.prototype.updatePoint = function(data, shift, noRedraw) {
    this.addPoint(data, shift, noRedraw);
};

/**
 * @param {*} ref
 * @param {?Boolean} noRedraw
 */
Rubix.AreaSeries.prototype.removePoint = function(ref, noRedraw) {
    if(this.chart_hidden) {
        this.temp_stack.push({
            type: 'removePoint',
            ref: ref
        });
        return;
    }

    if(!this.data[this.name]) {
        this.data[this.name] = [];
    }

    var removed = false;
    for(var i=0; i<this.data[this.name].length; i++) {
        if(this.data[this.name][i].x === ref) {
            this.data[this.name].splice(i, 1);
            removed = true;
            break;
        }
    }

    if(this.master_detail) {
        this.dataChanged = true;
    }

    if(noRedraw) return;

    if(this.setup) {
        this._animate_draw();
    } else {
        this.rubix.resetAxis(true);
        this.rubix.forceRedraw();
    }
};

/**
 * @param {Object} data
 * @param {?Boolean} shift
 * @param {?Boolean} noRedraw
 */
Rubix.AreaSeries.prototype.addPoint = function(data, shift, noRedraw) {
    this.rubix.data_changed = true;
    if(!(data instanceof Object) || (data instanceof Array)) {
        throw new Error("Object required for addPoint");
    }
    if(!(data.hasOwnProperty('x') || data.hasOwnProperty('y'))) {
        throw new Error("Object must be in the form: {x: ..., y: ...}");
    }

    if(this.chart_hidden) {
        this.temp_stack.push({
            type: 'addPoint',
            data: data,
            shift: shift
        });
        return;
    }

    if(!this.data[this.name]) {
        this.data[this.name] = [];
    }

    var added = false;
    for(var i=0; i<this.data[this.name].length; i++) {
        if(this.data[this.name][i].x === data.x) {
            this.data[this.name][i].y = data.y;
            added = true;
            break;
        }
    }

    if(!added) {
        this.data[this.name].push(data);
    }

    this.data[this.name].sort(function(a, b) {
        if(a.x > b.x) {
            return 1;
        } else if(a.x === b.x) {
            return 0;
        } else {
            return -1;
        }
    });

    if(this.rubix.opts.interval) {
        if(this.rubix.opts.interval < this.data[this.name].length) {
            this.data[this.name].shift();
        }
    } else {
        if(shift) {
            this.data[this.name].shift();
        }
    }

    if(this.master_detail) {
        this.dataChanged = true;
    }

    if(noRedraw) return;
    if(this.setup) {
        this._animate_draw();
    } else {
        this.rubix.resetAxis(true);
        this.rubix.forceRedraw();
    }
};

Rubix.AreaSeries.prototype._animate_draw = function(local) {
    var self = this;
    var text = this.root.selectAll('.y.axis').selectAll('text')[0];
    var width = [];
    for(var i=0; i<text.length; i++) {
        width.push(text[i].getBBox().width);
    }
    var origMaxWidth = d3.max(width);

    this.rubix.resetAxis(false);

    text = this.root.selectAll('.y.axis').selectAll('text')[0];
    width = [];
    for(var i=0; i<text.length; i++) {
        width.push(text[i].getBBox().width);
    }

    var maxWidth = d3.max(width);
    this.rubix.marginLeft(maxWidth);
    this.rubix.resetFocus();
};

Rubix.AreaSeries.prototype.hidden = function() {
    this.chart_hidden = true;
};

Rubix.AreaSeries.prototype.show = function() {
    this.chart_hidden = false;
    while(this.temp_stack.length>1) {
        var rawData = this.temp_stack.shift();
        if(rawData.type === 'addPoint') {
            this.addPoint(rawData.data, rawData.shift, true);
        } else {
            this.removePoint(rawData.ref);
        }
    }
    if(this.temp_stack.length) {
        var rawData = this.temp_stack.shift();
        if(rawData.type === 'addPoint') {
            this.addPoint(rawData.data, rawData.shift, true);
        } else {
            this.removePoint(rawData.ref);
        }
    }
};

Rubix.AreaSeries.prototype.globalRedraw = function(rubix) {
    this.area_series.selectAll('.'+this.id+'.line').attr('d', this.line);
    this.area_series.selectAll('.'+this.id+'.area').attr('d', this.area);
};

Rubix.AreaSeries.prototype.forceRedraw = function(rubix) {
    this.redraw(rubix);
};

Rubix.AreaSeries.prototype.on_complete_draw = function() {
    if(!this.setup) return;
    var self = this;
    var datum = this.data[this.name];
    if(this.opts.marker !== undefined && this.show_markers) {
        if(this.master_detail) {
            this.rubix.symbols.attr('clip-path', 'url(#'+self.rubix.master_id+'-clip-symbols)');
        }
        this.markers = this.rubix.symbols.selectAll('.'+this.id+'-marker');
        switch(this.opts.marker) {
            case 'circle':
            case 'cross':
            case 'square':
            case 'diamond':
            case 'triangle-up':
            case 'triangle-down':
                var symbol = d3.svg.symbol();
                symbol.type(this.opts.marker);

                var symbolType = this.markers.data(datum);
                symbolType.enter().append('path')
                      .attr('d', symbol)
                      .attr('class', this.id + '-marker')
                      .attr('fill', this.opts.color)
                      .style('display', function(d) {
                            if(d.y === null) {
                                return 'none';
                            }
                            return null;
                      })
                      .attr('stroke', 'white')
                      .attr('transform', function(d) {
                            var val = d.y;
                            if(isNaN(val)) {
                                val = 0;
                            }
                            if(self.rubix.opts.invertAxes) {
                                var _y = self.rubix.y(d.x);
                                var _x = self.rubix.x(val);
                                if(self.rubix.axis.y.range === 'column' || self.rubix.axis.y.range === 'bar') {
                                    _y += self.rubix.y.rangeBand()/2;
                                }
                            } else {
                                var _y = self.rubix.y(val);
                                var _x = self.rubix.x(d.x);
                                if(self.rubix.axis.x.range === 'column' || self.rubix.axis.x.range === 'bar') {
                                    _x += self.rubix.x.rangeBand()/2;
                                }
                            }
                            return 'translate('+_x+','+_y+')';
                      });
                symbolType.exit().remove();
            break;
            default:
                throw new Error('Unknown marker type : ' + this.opts.marker);
            break;
        }
    }
};

Rubix.AreaSeries.prototype.setupFocus = function() {
    if(!this.setup) return;
    if(this.focus) this.focus.remove();
    this.focus = this.rubix.focus_group.append('g');
    this.focus.attr('class', 'focus');
    this.focus.style('display', 'none');
    switch(this.opts.marker) {
        case 'circle':
        case 'cross':
        case 'square':
        case 'diamond':
        case 'triangle-up':
        case 'triangle-down':
            var symbol = d3.svg.symbol();
            symbol.type(this.opts.marker);
            var path = this.focus.append('path').attr('d', symbol).attr('fill', this.opts.color).attr('stroke', 'white').attr('stroke-width', 2);
        break;
        default:
            throw new Error('Unknown marker type : ' + this.opts.marker);
        break;
    }
};

Rubix.AreaSeries.prototype.on_focus = function() {
    if(!this.setup) return;
    this.linePath.attr('stroke-width', 2 * this.opts.strokewidth);
};

Rubix.AreaSeries.prototype.off_focus = function() {
    if(!this.setup) return;
    this.linePath.attr('stroke-width', 2 * this.opts.strokewidth);
};

Rubix.Cleanup = function() {
    var master_id = null;
    for(var i=0; i<RubixListeners.length; i++) {
        master_id = RubixListeners[i];
        $(window).off('orientationchange.'+master_id);
        $(window).off('rubix.redraw.'+master_id);
        $(window).off('resize.rubix.'+master_id);
        $(window).off('debouncedresize.rubix.'+master_id);
        $(window).off('throttledresize.rubix.'+master_id);
    }
    RubixListeners = [];
};

Rubix.redraw = function() {
    $(window).trigger('resize');
};
