/**
 * @fileOverview A D3 based distribution chart system. Supports: Box plots, Violin plots, Notched box plots, trend lines, beeswarm plot
 * @version 3.0
 */


/**
 * Creates a box plot, violin plot, and or notched box plot
 * @param settings Configuration options for the base plot
 * @param settings.data The data for the plot
 * @param settings.xName The name of the column that should be used for the x groups
 * @param settings.yName The name of the column used for the y values
 * @param {string} settings.selector The selector string for the main chart div
 * @param [settings.axisLabels={}] Defaults to the xName and yName
 * @param [settings.yTicks = 1] 1 = default ticks. 2 =  double, 0.5 = half
 * @param [settings.scale='linear'] 'linear' or 'log' - y scale of the chart
 * @param [settings.chartSize={width:800, height:400}] The height and width of the chart itself (doesn't include the container)
 * @param [settings.margin={top: 15, right: 60, bottom: 40, left: 50}] The margins around the chart (inside the main div)
 * @param [settings.constrainExtremes=false] Should the y scale include outliers?
 * @returns {object} chart A chart object
 */
function makeDistroChart(settings) {

    var chart = {};

    // Defaults
    chart.settings = {
        data: null,
        xName: null,
        yName: null,
        axisLabels: {xAxis: null, yAxis: null},
        labelName: "label",
        unitsName: "units",
        selector: null,
        axisLables: null,
        yTicks: 1,
        scale: 'linear',
        chartSize: {width: 800, height: 400},
        margin: {top: 15, right: 10, bottom: 50, left: 50},
        constrainExtremes: false,
        color: d3.scale.category10(),
        qctype: null
    };

    for (var setting in settings) {
        chart.settings[setting] = settings[setting]
    }

    function formatAsFloat(d) {
        if (d % 1 !== 0) {
            return d3.format(".2f")(d);
        } else {
            return d3.format(".0f")(d);
        }
    }

    function logFormatNumber(d) {
        var x = Math.log(d) / Math.log(10) + 1e-6;
        return Math.abs(x - Math.floor(x)) < 0.6 ? formatAsFloat(d) : "";
    }

    chart.yFormatter = formatAsFloat;

    chart.data = chart.settings.data;

    iqmName = chart.data[0][chart.settings.xName]
    if (iqmName.lastIndexOf('_') > 0) {
        iqmName = iqmName.substr(0, iqmName.lastIndexOf('_'))
    }
    chart.settings.axisLabels.yAxis = iqmName.toUpperCase()
    units = chart.data[0][chart.settings.unitsName]
    if (units) {
        chart.settings.axisLabels.yAxis += ' (' + units + ')'
    }


    chart.groupObjs = {}; //The data organized by grouping and sorted as well as any metadata for the groups
    chart.objs = {mainDiv: null, chartDiv: null, g: null, xAxis: null, yAxis: null};
    chart.colorFunct = null;

    /**
     * Takes an array, function, or object mapping and created a color function from it
     * @param {function|[]|object} colorOptions
     * @returns {function} Function to be used to determine chart colors
     */
    function getColorFunct(colorOptions) {
        if (typeof colorOptions == 'function') {
            return colorOptions
        } else if (Array.isArray(colorOptions)) {
            //  If an array is provided, map it to the domain
            var colorMap = {}, cColor = 0;
            for (var cName in chart.groupObjs) {
                colorMap[cName] = colorOptions[cColor];
                cColor = (cColor + 1) % colorOptions.length;
            }
            return function (group) {
                return colorMap[group];
            }
        } else if (typeof colorOptions == 'object') {
            // if an object is provided, assume it maps to  the colors
            return function (group) {
                return colorOptions[group];
            }
        } else {
            return d3.scale.category10();
        }
    }

    /**
     * Takes a percentage as returns the values that correspond to that percentage of the group range witdh
     * @param objWidth Percentage of range band
     * @param gName The bin name to use to get the x shift
     * @returns {{left: null, right: null, middle: null}}
     */
    function getObjWidth(objWidth, gName) {
        var objSize = {left: null, right: null, middle: null};
        var width = chart.xScale.rangeBand() * (objWidth / 100);
        var padding = (chart.xScale.rangeBand() - width) / 2;
        var gShift = chart.xScale(gName);
        objSize.middle = chart.xScale.rangeBand() / 2 + gShift;
        objSize.left = padding + gShift;
        objSize.right = objSize.left + width;
        return objSize;
    }

    /**
     * Adds jitter to the  scatter point plot
     * @param doJitter true or false, add jitter to the point
     * @param width percent of the range band to cover with the jitter
     * @returns {number}
     */
    function addJitter(doJitter, width) {
        if (doJitter !== true || width == 0) {
            return 0
        }
        return Math.floor(Math.random() * width) - width / 2;
    }

    function shallowCopy(oldObj) {
        var newObj = {};
        for (var i in oldObj) {
            if (oldObj.hasOwnProperty(i)) {
                newObj[i] = oldObj[i];
            }
        }
        return newObj;
    }

    /**
     * Closure that creates the tooltip hover function
     * @param groupName Name of the x group
     * @param metrics Object to use to get values for the group
     * @returns {Function} A function that provides the values for the tooltip
     */
    function tooltipHover(groupName, metrics) {
        var tooltipString = "Group: " + groupName;
        tooltipString += "<br\>Max: " + formatAsFloat(metrics.max, 0.1);
        tooltipString += "<br\>Q3: " + formatAsFloat(metrics.quartile3);
        tooltipString += "<br\>Median: " + formatAsFloat(metrics.median);
        tooltipString += "<br\>Q1: " + formatAsFloat(metrics.quartile1);
        tooltipString += "<br\>Min: " + formatAsFloat(metrics.min);
        return function () {
            chart.objs.tooltip.transition().duration(200).style("opacity", 0.9);
            chart.objs.tooltip.html(tooltipString)
        };
    }

    function axislabelHover(groupName) {
        var tooltipString = "Go to definition of " + groupName;
        return function () {
            chart.objs.tooltip.transition().duration(200).style("opacity", 1.0);
            chart.objs.tooltip.html(tooltipString)
        };
    }

    /**
     * Closure that creates the tooltip hover function
     * @param groupName Name of the x group
     * @param metrics Object to use to get values for the group
     * @returns {Function} A function that provides the values for the tooltip
     */
    function pointHover(label, value) {
        var tooltipString = "Subject: " + label + "<br\>Measure: " + value
        return function () {
            chart.objs.tooltip.transition().duration(200).style("opacity", 1.0);
            chart.objs.tooltip.html(tooltipString)
        };
    }

    /**
     * Parse the data and calculates base values for the plots
     */
    !function prepareData() {
        function calcMetrics(values) {
            // Do not reorder in-place
            values = values.slice(0).sort(d3.ascending)

            var metrics = { //These are the original non�scaled values
                max: null,
                upperOuterFence: null,
                upperInnerFence: null,
                quartile3: null,
                median: null,
                mean: null,
                iqr: null,
                quartile1: null,
                lowerInnerFence: null,
                lowerOuterFence: null,
                min: null
            };

            metrics.min = d3.min(values);
            metrics.quartile1 = d3.quantile(values, 0.25);
            metrics.median = d3.median(values);
            metrics.mean = d3.mean(values);
            metrics.quartile3 = d3.quantile(values, 0.75);
            metrics.max = d3.max(values);
            metrics.iqr = metrics.quartile3 - metrics.quartile1;

            //The inner fences are the closest value to the IQR without going past it (assumes sorted lists)
            var LIF = metrics.quartile1 - (1.5 * metrics.iqr);
            var UIF = metrics.quartile3 + (1.5 * metrics.iqr);
            for (var i = 0; i <= values.length; i++) {
                if (values[i] < LIF) {
                    continue;
                }
                if (!metrics.lowerInnerFence && values[i] >= LIF) {
                    metrics.lowerInnerFence = values[i];
                    continue;
                }
                if (values[i] > UIF) {
                    metrics.upperInnerFence = values[i - 1];
                    break;
                }
            }


            metrics.lowerOuterFence = metrics.quartile1 - (3 * metrics.iqr);
            metrics.upperOuterFence = metrics.quartile3 + (3 * metrics.iqr);
            if (!metrics.lowerInnerFence) {
                metrics.lowerInnerFence = metrics.min;
            }
            if (!metrics.upperInnerFence) {
                metrics.upperInnerFence = metrics.max;
            }
            return metrics
        }

        var current_x = null;
        var current_y = null;
        var current_row;

        // Group the values
        for (current_row = 0; current_row < chart.data.length; current_row++) {
            current_x = chart.data[current_row][chart.settings.xName];
            current_y = chart.data[current_row][chart.settings.yName];
            current_label = chart.data[current_row][chart.settings.labelName];

            if (chart.groupObjs.hasOwnProperty(current_x)) {
                chart.groupObjs[current_x].values.push(current_y);
                chart.groupObjs[current_x].labels.push(current_label);
            } else {
                chart.groupObjs[current_x] = {};
                chart.groupObjs[current_x].values = [current_y];
                chart.groupObjs[current_x].labels = [current_label];
            }
        }

        for (var cName in chart.groupObjs) {
            // chart.groupObjs[cName].values.sort(d3.ascending);
            chart.groupObjs[cName].metrics = {};
            chart.groupObjs[cName].metrics = calcMetrics(chart.groupObjs[cName].values);

        }
    }();

    /**
     * Prepare the chart settings and chart div and svg
     */
    !function prepareSettings() {
        //Set base settings
        chart.margin = chart.settings.margin;
        chart.divWidth = chart.settings.chartSize.width;
        chart.divHeight = chart.settings.chartSize.height;
        chart.width = chart.divWidth - chart.margin.left - chart.margin.right;
        chart.height = chart.divHeight - chart.margin.top - chart.margin.bottom;

        if (chart.settings.axisLabels) {
            chart.xAxisLable = chart.settings.axisLabels.xAxis;
            chart.yAxisLable = chart.settings.axisLabels.yAxis;
        }

        if (chart.settings.scale === 'log') {
            chart.yScale = d3.scale.log();
            chart.yFormatter = logFormatNumber;
        } else {
            chart.yScale = d3.scale.linear();
        }

        if (chart.settings.constrainExtremes === true) {
            var fences = [];
            for (var cName in chart.groupObjs) {
                fences.push(chart.groupObjs[cName].metrics.lowerInnerFence);
                fences.push(chart.groupObjs[cName].metrics.upperInnerFence);
            }
            chart.range = d3.extent(fences);

        } else {
            chart.range = d3.extent(chart.data, function (d) {return d[chart.settings.yName];});
        }

        chart.colorFunct = getColorFunct(chart.settings.colors);

        // Build Scale functions
        chart.yScale.range([chart.height, 0]).domain(chart.range).nice().clamp(true);
        chart.xScale = d3.scale.ordinal().domain(Object.keys(chart.groupObjs)).rangeBands([0, chart.width]);

        //Build Axes Functions
        chart.objs.yAxis = d3.svg.axis()
            .scale(chart.yScale)
            .orient("left")
            .tickFormat(chart.yFormatter)
            .outerTickSize(0)
            .innerTickSize(-chart.width + (chart.margin.right + chart.margin.left));
        chart.objs.yAxis.ticks(chart.objs.yAxis.ticks()*chart.settings.yTicks);
        chart.objs.xAxis = d3.svg.axis().scale(chart.xScale).orient("bottom").tickSize(5);
    }();

    /**
     * Updates the chart based on the current settings and window size
     * @returns {*}
     */
    chart.update = function () {
        // Update chart size based on view port size
        chart.width = parseInt(chart.objs.chartDiv.style("width"), 10) - (chart.margin.left + chart.margin.right);
        chart.height = parseInt(chart.objs.chartDiv.style("height"), 10) - (chart.margin.top + chart.margin.bottom);

        // Update scale functions
        chart.xScale.rangeBands([0, chart.width]);
        chart.yScale.range([chart.height, 0]);

        // Update the yDomain if the Violin plot clamp is set to -1 meaning it will extend the violins to make nice points
        if (chart.violinPlots && chart.violinPlots.options.show == true && chart.violinPlots.options._yDomainVP != null) {
            chart.yScale.domain(chart.violinPlots.options._yDomainVP).nice().clamp(true);
        } else {
            chart.yScale.domain(chart.range).nice().clamp(true);
        }

        //Update axes
        chart.objs.g.select('.x.axis').attr("transform", "translate(0," + chart.height + ")").call(chart.objs.xAxis)
            .selectAll("text")
            .attr("y", 5)
            .attr("x", -5)
            .attr("transform", "rotate(-45)")
            .style("text-anchor", "end");
        chart.objs.g.select('.x.axis .label').attr("x", chart.width / 2);
        chart.objs.g.select('.y.axis').call(chart.objs.yAxis.innerTickSize(-chart.width));
        chart.objs.g.select('.y.axis .label').attr("x", -chart.height / 2);
        chart.objs.chartDiv.select('svg').attr("width", chart.width + (chart.margin.left + chart.margin.right)).attr("height", chart.height + (chart.margin.top + chart.margin.bottom));

        return chart;
    };

    /**
     * Prepare the chart html elements
     */
    !function prepareChart() {
        // Build main div and chart div
        chart.objs.mainDiv = d3.select(chart.settings.selector)
            .style("width", chart.divWidth + "px")
            .style("display", "inline-block");
        // Add all the divs to make it centered and responsive
        chart.objs.mainDiv.append("div")
            .attr("class", "inner-wrapper")
            .style("padding-bottom", (chart.divHeight / chart.divWidth) * 100 + "%")
            .append("div").attr("class", "outer-box")
            .append("div").attr("class", "inner-box");
        // Capture the inner div for the chart (where the chart actually is)
        chart.selector = chart.settings.selector + " .inner-box";
        chart.objs.chartDiv = d3.select(chart.selector);
        d3.select(window).on('resize.' + chart.selector, chart.update);

        // Create the svg
        chart.objs.g = chart.objs.chartDiv.append("svg")
            .attr("class", "chart-area")
            .attr("width", chart.width + (chart.margin.left + chart.margin.right))
            .attr("height", chart.height + (chart.margin.top + chart.margin.bottom))
            .append("g")
            .attr("transform", "translate(" + chart.margin.left + "," + chart.margin.top + ")");

        // Create axes
        chart.objs.axes = chart.objs.g.append("g").attr("class", "axis");
        chart.objs.axes.append("g")
            .attr("class", "x axis")
            .attr("transform", "translate(0," + chart.height + ")")
            .call(chart.objs.xAxis);
        chart.objs.axes.append("g")
            .attr("class", "y axis")
            .call(chart.objs.yAxis)
            .append("text")
            //.attr("class", "label")
            .attr("transform", "rotate(-90)")
            //.attr("y", -42)
            .attr("y", 6)
            .attr("dy", ".71em")
            //.attr("x", -chart.height / 2)
            .style("text-anchor", "end")
            .style("font-size", "16px")
            .append("a")
            .attr("xlink:href", function(d) {
                return "http://mriqc.readthedocs.io/en/latest/measures.html"
            })
            .text(chart.yAxisLable)
            .on("mouseover", function () {
                chart.objs.tooltip
                    .style("display", null)
                    .style("left", (d3.event.pageX) + "px")
                    .style("top", (d3.event.pageY - 28) + "px");
            }).on("mouseout", function () {
                chart.objs.tooltip.style("display", "none");
            }).on("mousemove", axislabelHover(chart.yAxisLable));

        // Create tooltip div
        chart.objs.tooltip = chart.objs.mainDiv.append('div').attr('class', 'tooltip');
        for (var cName in chart.groupObjs) {
            chart.groupObjs[cName].g = chart.objs.g.append("g").attr("class", "group");
        }
        chart.update();
    }();

    /**
     * Render a violin plot on the current chart
     * @param options
     * @param [options.showViolinPlot=true] True or False, show the violin plot
     * @param [options.resolution=100 default]
     * @param [options.bandwidth=10 default] May need higher bandwidth for larger data sets
     * @param [options.width=50] The max percent of the group rangeBand that the violin can be
     * @param [options.interpolation=''] How to render the violin
     * @param [options.clamp=0 default]
     *   0 = keep data within chart min and max, clamp once data = 0. May extend beyond data set min and max
     *   1 = clamp at min and max of data set. Possibly no tails
     *  -1 = extend chart axis to make room for data to interpolate to 0. May extend axis and data set min and max
     * @param [options.colors=chart default] The color mapping for the violin plot
     * @returns {*} The chart object
     */
    chart.renderViolinPlot = function (options) {
        chart.violinPlots = {};

        var defaultOptions = {
            show: true,
            showViolinPlot: true,
            resolution: 100,
            bandwidth: 20,
            width: 50,
            interpolation: 'cardinal',
            clamp: 1,
            colors: chart.colorFunct,
            _yDomainVP: null // If the Violin plot is set to close all violin plots, it may need to extend the domain, that extended domain is stored here
        };
        chart.violinPlots.options = shallowCopy(defaultOptions);
        for (var option in options) {
            chart.violinPlots.options[option] = options[option]
        }
        var vOpts = chart.violinPlots.options;

        // Create violin plot objects
        for (var cName in chart.groupObjs) {
            chart.groupObjs[cName].violin = {};
            chart.groupObjs[cName].violin.objs = {};
        }

        /**
         * Take a new set of options and redraw the violin
         * @param updateOptions
         */
        chart.violinPlots.change = function (updateOptions) {
            if (updateOptions) {
                for (var key in updateOptions) {
                    vOpts[key] = updateOptions[key]
                }
            }

            for (var cName in chart.groupObjs) {
                chart.groupObjs[cName].violin.objs.g.remove()
            }

            chart.violinPlots.prepareViolin();
            chart.violinPlots.update();
        };

        chart.violinPlots.reset = function () {
            chart.violinPlots.change(defaultOptions)
        };
        chart.violinPlots.show = function (opts) {
            if (opts !== undefined) {
                opts.show = true;
                if (opts.reset) {
                    chart.violinPlots.reset()
                }
            } else {
                opts = {show: true};
            }
            chart.violinPlots.change(opts);

        };

        chart.violinPlots.hide = function (opts) {
            if (opts !== undefined) {
                opts.show = false;
                if (opts.reset) {
                    chart.violinPlots.reset()
                }
            } else {
                opts = {show: false};
            }
            chart.violinPlots.change(opts);

        };

        /**
         * Update the violin obj values
         */
        chart.violinPlots.update = function () {
            var cName, cViolinPlot;

            for (cName in chart.groupObjs) {
                cViolinPlot = chart.groupObjs[cName].violin;

                // Build the violins sideways, so use the yScale for the xScale and make a new yScale
                var xVScale = chart.yScale.copy();


                // Create the Kernel Density Estimator Function
                cViolinPlot.kde = kernelDensityEstimator(eKernel(vOpts.bandwidth), xVScale.ticks(vOpts.resolution));
                cViolinPlot.kdedata = cViolinPlot.kde(chart.groupObjs[cName].values);

                var interpolateMax = chart.groupObjs[cName].metrics.max,
                    interpolateMin = chart.groupObjs[cName].metrics.min;

                if (vOpts.clamp == 0 || vOpts.clamp == -1) { //
                    // When clamp is 0, calculate the min and max that is needed to bring the violin plot to a point
                    // interpolateMax = the Minimum value greater than the max where y = 0
                    interpolateMax = d3.min(cViolinPlot.kdedata.filter(function (d) {
                        return (d.x > chart.groupObjs[cName].metrics.max && d.y == 0)
                    }), function (d) {
                        return d.x;
                    });
                    // interpolateMin = the Maximum value less than the min where y = 0
                    interpolateMin = d3.max(cViolinPlot.kdedata.filter(function (d) {
                        return (d.x < chart.groupObjs[cName].metrics.min && d.y == 0)
                    }), function (d) {
                        return d.x;
                    });
                    // If clamp is -1 we need to extend the axises so that the violins come to a point
                    if (vOpts.clamp == -1) {
                        kdeTester = eKernelTest(eKernel(vOpts.bandwidth), chart.groupObjs[cName].values);
                        if (!interpolateMax) {
                            var interMaxY = kdeTester(chart.groupObjs[cName].metrics.max);
                            var interMaxX = chart.groupObjs[cName].metrics.max;
                            var count = 25; // Arbitrary limit to make sure we don't get an infinite loop
                            while (count > 0 && interMaxY != 0) {
                                interMaxY = kdeTester(interMaxX);
                                interMaxX += 1;
                                count -= 1;
                            }
                            interpolateMax = interMaxX;
                        }
                        if (!interpolateMin) {
                            var interMinY = kdeTester(chart.groupObjs[cName].metrics.min);
                            var interMinX = chart.groupObjs[cName].metrics.min;
                            var count = 25;  // Arbitrary limit to make sure we don't get an infinite loop
                            while (count > 0 && interMinY != 0) {
                                interMinY = kdeTester(interMinX);
                                interMinX -= 1;
                                count -= 1;
                            }
                            interpolateMin = interMinX;
                        }

                    }
                    // Check to see if the new values are outside the existing chart range
                    //   If they are assign them to the master _yDomainVP
                    if (!vOpts._yDomainVP) vOpts._yDomainVP = chart.range.slice(0);
                    if (interpolateMin && interpolateMin < vOpts._yDomainVP[0]) {
                        vOpts._yDomainVP[0] = interpolateMin;
                    }
                    if (interpolateMax && interpolateMax > vOpts._yDomainVP[1]) {
                        vOpts._yDomainVP[1] = interpolateMax;
                    }


                }


                if (vOpts.showViolinPlot) {
                    chart.update();
                    xVScale = chart.yScale.copy();

                    // Need to recalculate the KDE because the xVScale changed
                    cViolinPlot.kde = kernelDensityEstimator(eKernel(vOpts.bandwidth), xVScale.ticks(vOpts.resolution));
                    cViolinPlot.kdedata = cViolinPlot.kde(chart.groupObjs[cName].values);
                }

                cViolinPlot.kdedata = cViolinPlot.kdedata
                    .filter(function (d) {
                        return (!interpolateMin || d.x >= interpolateMin)
                    })
                    .filter(function (d) {
                        return (!interpolateMax || d.x <= interpolateMax)
                    });
            }
            for (cName in chart.groupObjs) {
                cViolinPlot = chart.groupObjs[cName].violin;

                // Get the violin width
                var objBounds = getObjWidth(vOpts.width, cName);
                var width = (objBounds.right - objBounds.left) / 2;

                var yVScale = d3.scale.linear()
                    .range([width, 0])
                    .domain([0, d3.max(cViolinPlot.kdedata, function (d) {return d.y;})])
                    .clamp(true);

                var area = d3.svg.area()
                    .interpolate(vOpts.interpolation)
                    .x(function (d) {return xVScale(d.x);})
                    .y0(width)
                    .y1(function (d) {return yVScale(d.y);});

                var line = d3.svg.line()
                    .interpolate(vOpts.interpolation)
                    .x(function (d) {return xVScale(d.x);})
                    .y(function (d) {return yVScale(d.y)});

                if (cViolinPlot.objs.left.area) {
                    cViolinPlot.objs.left.area
                        .datum(cViolinPlot.kdedata)
                        .attr("d", area);
                    cViolinPlot.objs.left.line
                        .datum(cViolinPlot.kdedata)
                        .attr("d", line);

                    cViolinPlot.objs.right.area
                        .datum(cViolinPlot.kdedata)
                        .attr("d", area);
                    cViolinPlot.objs.right.line
                        .datum(cViolinPlot.kdedata)
                        .attr("d", line);
                }

                // Rotate the violins
                cViolinPlot.objs.left.g.attr("transform", "rotate(90,0,0)   translate(0,-" + objBounds.left + ")  scale(1,-1)");
                cViolinPlot.objs.right.g.attr("transform", "rotate(90,0,0)  translate(0,-" + objBounds.right + ")");
            }
        };

        /**
         * Create the svg elements for the violin plot
         */
        chart.violinPlots.prepareViolin = function () {
            var cName, cViolinPlot;

            if (vOpts.colors) {
                chart.violinPlots.color = getColorFunct(vOpts.colors);
            } else {
                chart.violinPlots.color = chart.colorFunct
            }

            if (vOpts.show == false) {return}

            for (cName in chart.groupObjs) {
                cViolinPlot = chart.groupObjs[cName].violin;

                cViolinPlot.objs.g = chart.groupObjs[cName].g.append("g").attr("class", "violin-plot");
                cViolinPlot.objs.left = {area: null, line: null, g: null};
                cViolinPlot.objs.right = {area: null, line: null, g: null};

                cViolinPlot.objs.left.g = cViolinPlot.objs.g.append("g");
                cViolinPlot.objs.right.g = cViolinPlot.objs.g.append("g");

                if (vOpts.showViolinPlot !== false) {
                    //Area
                    cViolinPlot.objs.left.area = cViolinPlot.objs.left.g.append("path")
                        .attr("class", "area")
                        .style("fill", chart.violinPlots.color(cName));
                    cViolinPlot.objs.right.area = cViolinPlot.objs.right.g.append("path")
                        .attr("class", "area")
                        .style("fill", chart.violinPlots.color(cName));

                    //Lines
                    cViolinPlot.objs.left.line = cViolinPlot.objs.left.g.append("path")
                        .attr("class", "line")
                        .attr("fill", 'none')
                        .style("stroke", chart.violinPlots.color(cName));
                    cViolinPlot.objs.right.line = cViolinPlot.objs.right.g.append("path")
                        .attr("class", "line")
                        .attr("fill", 'none')
                        .style("stroke", chart.violinPlots.color(cName));
                }

            }

        };


        function kernelDensityEstimator(kernel, x) {
            return function (sample) {
                return x.map(function (x) {
                    return {x:x, y:d3.mean(sample, function (v) {return kernel(x - v);})};
                });
            };
        }

        function eKernel(scale) {
            return function (u) {
                return Math.abs(u /= scale) <= 1 ? .75 * (1 - u * u) / scale : 0;
            };
        }

        // Used to find the roots for adjusting violin axis
        // Given an array, find the value for a single point, even if it is not in the domain
        function eKernelTest(kernel, array) {
            return function (testX) {
                return d3.mean(array, function (v) {return kernel(testX - v);})
            }
        }

        chart.violinPlots.prepareViolin();

        d3.select(window).on('resize.' + chart.selector + '.violinPlot', chart.violinPlots.update);
        chart.violinPlots.update();
        return chart;
    };

    /**
     * Render a box plot on the current chart
     * @param options
     * @param [options.show=true] Toggle the whole plot on and off
     * @param [options.showBox=true] Show the box part of the box plot
     * @param [options.showWhiskers=true] Show the whiskers
     * @param [options.showMedian=true] Show the median line
     * @param [options.showMean=false] Show the mean line
     * @param [options.medianCSize=3] The size of the circle on the median
     * @param [options.showOutliers=true] Plot outliers
     * @param [options.boxwidth=30] The max percent of the group rangeBand that the box can be
     * @param [options.lineWidth=boxWidth] The max percent of the group rangeBand that the line can be
     * @param [options.outlierScatter=false] Spread out the outliers so they don't all overlap (in development)
     * @param [options.outlierCSize=2] Size of the outliers
     * @param [options.colors=chart default] The color mapping for the box plot
     * @returns {*} The chart object
     */
    chart.renderBoxPlot = function (options) {
        chart.boxPlots = {};

        // Defaults
        var defaultOptions = {
            show: true,
            showBox: true,
            showWhiskers: true,
            showMedian: true,
            showMean: false,
            medianCSize: 3.5,
            showOutliers: true,
            boxWidth: 30,
            lineWidth: null,
            scatterOutliers: false,
            outlierCSize: 2.5,
            colors: chart.colorFunct,
            padding: 0
        };
        chart.boxPlots.options = shallowCopy(defaultOptions);
        for (var option in options) {
            chart.boxPlots.options[option] = options[option]
        }
        var bOpts = chart.boxPlots.options;

        //Create box plot objects
        for (var cName in chart.groupObjs) {
            chart.groupObjs[cName].boxPlot = {};
            chart.groupObjs[cName].boxPlot.objs = {};
        }


        /**
         * Calculates all the outlier points for each group
         */
        !function calcAllOutliers() {

            /**
             * Create lists of the outliers for each content group
             * @param cGroup The object to modify
             * @return null Modifies the object in place
             */
            function calcOutliers(cGroup) {
                var cExtremes = [];
                var cOutliers = [];
                var cOut, idx;
                for (idx = 0; idx <= cGroup.values.length; idx++) {
                    cOut = {value: cGroup.values[idx]};

                    if (cOut.value < cGroup.metrics.lowerInnerFence) {
                        if (cOut.value < cGroup.metrics.lowerOuterFence) {
                            cExtremes.push(cOut);
                        } else {
                            cOutliers.push(cOut);
                        }
                    } else if (cOut.value > cGroup.metrics.upperInnerFence) {
                        if (cOut.value > cGroup.metrics.upperOuterFence) {
                            cExtremes.push(cOut);
                        } else {
                            cOutliers.push(cOut);
                        }
                    }
                }
                cGroup.boxPlot.objs.outliers = cOutliers;
                cGroup.boxPlot.objs.extremes = cExtremes;
            }

            for (var cName in chart.groupObjs) {
                calcOutliers(chart.groupObjs[cName]);
            }
        }();

        /**
         * Take updated options and redraw the box plot
         * @param updateOptions
         */
        chart.boxPlots.change = function (updateOptions) {
            if (updateOptions) {
                for (var key in updateOptions) {
                    bOpts[key] = updateOptions[key]
                }
            }

            for (var cName in chart.groupObjs) {
                chart.groupObjs[cName].boxPlot.objs.g.remove()
            }
            chart.boxPlots.prepareBoxPlot();
            chart.boxPlots.update()
        };

        chart.boxPlots.reset = function () {
            chart.boxPlots.change(defaultOptions)
        };
        chart.boxPlots.show = function (opts) {
            if (opts !== undefined) {
                opts.show = true;
                if (opts.reset) {
                    chart.boxPlots.reset()
                }
            } else {
                opts = {show: true};
            }
            chart.boxPlots.change(opts)

        };
        chart.boxPlots.hide = function (opts) {
            if (opts !== undefined) {
                opts.show = false;
                if (opts.reset) {
                    chart.boxPlots.reset()
                }
            } else {
                opts = {show: false};
            }
            chart.boxPlots.change(opts)
        };

        /**
         * Update the box plot obj values
         */
        chart.boxPlots.update = function () {
            var cName, cBoxPlot;

            for (cName in chart.groupObjs) {
                cBoxPlot = chart.groupObjs[cName].boxPlot;

                // Get the box width
                var objBounds = getObjWidth(bOpts.boxWidth, cName);
                objBounds.middle += chart.boxPlots.options.padding
                objBounds.right += chart.boxPlots.options.padding
                objBounds.left += chart.boxPlots.options.padding
                var width = (objBounds.right - objBounds.left);

                var sMetrics = {}; //temp var for scaled (plottable) metric values
                for (var attr in chart.groupObjs[cName].metrics) {
                    sMetrics[attr] = null;
                    sMetrics[attr] = chart.yScale(chart.groupObjs[cName].metrics[attr]);
                }

                // Box
                if (cBoxPlot.objs.box) {
                    cBoxPlot.objs.box
                        .attr("x", objBounds.left)
                        .attr('width', width)
                        .attr("y", sMetrics.quartile3)
                        .attr("rx", 1)
                        .attr("ry", 1)
                        .attr("height", -sMetrics.quartile3 + sMetrics.quartile1)
                }

                // Lines
                var lineBounds = null;
                if (bOpts.lineWidth) {
                    lineBounds = getObjWidth(bOpts.lineWidth, cName)
                } else {
                    lineBounds = objBounds
                }

                // Apply padding
                lineBounds.middle += chart.boxPlots.options.padding
                lineBounds.right += chart.boxPlots.options.padding
                lineBounds.left += chart.boxPlots.options.padding

                // --Whiskers
                if (cBoxPlot.objs.upperWhisker) {
                    cBoxPlot.objs.upperWhisker.fence
                        .attr("x1", lineBounds.left)
                        .attr("x2", lineBounds.right)
                        .attr('y1', sMetrics.upperInnerFence)
                        .attr("y2", sMetrics.upperInnerFence);
                    cBoxPlot.objs.upperWhisker.line
                        .attr("x1", lineBounds.middle)
                        .attr("x2", lineBounds.middle)
                        .attr('y1', sMetrics.quartile3)
                        .attr("y2", sMetrics.upperInnerFence);

                    cBoxPlot.objs.lowerWhisker.fence
                        .attr("x1", lineBounds.left)
                        .attr("x2", lineBounds.right)
                        .attr('y1', sMetrics.lowerInnerFence)
                        .attr("y2", sMetrics.lowerInnerFence);
                    cBoxPlot.objs.lowerWhisker.line
                        .attr("x1", lineBounds.middle)
                        .attr("x2", lineBounds.middle)
                        .attr('y1', sMetrics.quartile1)
                        .attr("y2", sMetrics.lowerInnerFence);
                }

                // --Median
                if (cBoxPlot.objs.median) {
                    cBoxPlot.objs.median.line
                        .attr("x1", lineBounds.left)
                        .attr("x2", lineBounds.right)
                        .attr('y1', sMetrics.median)
                        .attr("y2", sMetrics.median);
                    cBoxPlot.objs.median.circle
                        .attr("cx", lineBounds.middle)
                        .attr("cy", sMetrics.median)
                }

                // --Mean
                if (cBoxPlot.objs.mean) {
                    cBoxPlot.objs.mean.line
                        .attr("x1", lineBounds.left)
                        .attr("x2", lineBounds.right)
                        .attr('y1', sMetrics.mean)
                        .attr("y2", sMetrics.mean);
                    cBoxPlot.objs.mean.circle
                        .attr("cx", lineBounds.middle)
                        .attr("cy", sMetrics.mean);
                }

                // Outliers

                var pt;
                if (cBoxPlot.objs.outliers) {
                    for (pt in cBoxPlot.objs.outliers) {
                        cBoxPlot.objs.outliers[pt].point
                            .attr("cx", objBounds.middle + addJitter(bOpts.scatterOutliers, width))
                            .attr("cy", chart.yScale(cBoxPlot.objs.outliers[pt].value));
                    }
                }
                if (cBoxPlot.objs.extremes) {
                    for (pt in cBoxPlot.objs.extremes) {
                        cBoxPlot.objs.extremes[pt].point
                            .attr("cx", objBounds.middle + addJitter(bOpts.scatterOutliers, width))
                            .attr("cy", chart.yScale(cBoxPlot.objs.extremes[pt].value));
                    }
                }
            }
        };

        /**
         * Create the svg elements for the box plot
         */
        chart.boxPlots.prepareBoxPlot = function () {
            var cName, cBoxPlot;

            if (bOpts.colors) {
                chart.boxPlots.colorFunct = getColorFunct(bOpts.colors);
            } else {
                chart.boxPlots.colorFunct = chart.colorFunct
            }

            if (bOpts.show == false) {
                return
            }

            for (cName in chart.groupObjs) {
                cBoxPlot = chart.groupObjs[cName].boxPlot;

                cBoxPlot.objs.g = chart.groupObjs[cName].g.append("g").attr("class", "box-plot");

                //Plot Box (default show)
                if (bOpts.showBox) {
                    cBoxPlot.objs.box = cBoxPlot.objs.g.append("rect")
                        .attr("class", "box")
                        .style("fill", chart.boxPlots.colorFunct(cName))
                        .style("stroke", chart.boxPlots.colorFunct(cName))
                        .on("mouseover", function () {
                            chart.objs.tooltip
                                .style("display", null)
                                .style("left", (d3.event.pageX) + "px")
                                .style("top", (d3.event.pageY - 28) + "px");
                        }).on("mouseout", function () {
                            chart.objs.tooltip.style("display", "none");
                        }).on("mousemove", tooltipHover(cName, chart.groupObjs[cName].metrics));
                    //A stroke is added to the box with the group color, it is
                    // hidden by default and can be shown through css with stroke-width
                }

                //Plot Median (default show)
                if (bOpts.showMedian) {
                    cBoxPlot.objs.median = {line: null, circle: null};
                    cBoxPlot.objs.median.line = cBoxPlot.objs.g.append("line")
                        .attr("class", "median");
                    cBoxPlot.objs.median.circle = cBoxPlot.objs.g.append("circle")
                        .attr("class", "median")
                        .attr('r', bOpts.medianCSize)
                        .style("fill", chart.boxPlots.colorFunct(cName));
                }

                // Plot Mean (default no plot)
                if (bOpts.showMean) {
                    cBoxPlot.objs.mean = {line: null, circle: null};
                    cBoxPlot.objs.mean.line = cBoxPlot.objs.g.append("line")
                        .attr("class", "mean");
                    cBoxPlot.objs.mean.circle = cBoxPlot.objs.g.append("circle")
                        .attr("class", "mean")
                        .attr('r', bOpts.medianCSize)
                        .style("fill", chart.boxPlots.colorFunct(cName));
                }

                // Plot Whiskers (default show)
                if (bOpts.showWhiskers) {
                    cBoxPlot.objs.upperWhisker = {fence: null, line: null};
                    cBoxPlot.objs.lowerWhisker = {fence: null, line: null};
                    cBoxPlot.objs.upperWhisker.fence = cBoxPlot.objs.g.append("line")
                        .attr("class", "upper whisker")
                        .style("stroke", chart.boxPlots.colorFunct(cName));
                    cBoxPlot.objs.upperWhisker.line = cBoxPlot.objs.g.append("line")
                        .attr("class", "upper whisker")
                        .style("stroke", chart.boxPlots.colorFunct(cName));

                    cBoxPlot.objs.lowerWhisker.fence = cBoxPlot.objs.g.append("line")
                        .attr("class", "lower whisker")
                        .style("stroke", chart.boxPlots.colorFunct(cName));
                    cBoxPlot.objs.lowerWhisker.line = cBoxPlot.objs.g.append("line")
                        .attr("class", "lower whisker")
                        .style("stroke", chart.boxPlots.colorFunct(cName));
                }

                // Plot outliers (default show)
                if (bOpts.showOutliers) {
                    if (!cBoxPlot.objs.outliers) calcAllOutliers();
                    var pt;
                    if (cBoxPlot.objs.outliers.length) {
                        var outDiv = cBoxPlot.objs.g.append("g").attr("class", "boxplot outliers");
                        for (pt in cBoxPlot.objs.outliers) {
                            cBoxPlot.objs.outliers[pt].point = outDiv.append("circle")
                                .attr("class", "outlier")
                                .attr('r', bOpts.outlierCSize)
                                .style("fill", chart.boxPlots.colorFunct(cName));
                        }
                    }

                    if (cBoxPlot.objs.extremes.length) {
                        var extDiv = cBoxPlot.objs.g.append("g").attr("class", "boxplot extremes");
                        for (pt in cBoxPlot.objs.extremes) {
                            cBoxPlot.objs.extremes[pt].point = extDiv.append("circle")
                                .attr("class", "extreme")
                                .attr('r', bOpts.outlierCSize)
                                .style("stroke", chart.boxPlots.colorFunct(cName));
                        }
                    }
                }


            }
        };
        chart.boxPlots.prepareBoxPlot();

        d3.select(window).on('resize.' + chart.selector + '.boxPlot', chart.boxPlots.update);
        chart.boxPlots.update();
        return chart;

    };

    /**
     * Render a notched box on the current chart
     * @param options
     * @param [options.show=true] Toggle the whole plot on and off
     * @param [options.showNotchBox=true] Show the notch box
     * @param [options.showLines=false] Show lines at the confidence intervals
     * @param [options.boxWidth=35] The width of the widest part of the box
     * @param [options.medianWidth=20] The width of the narrowist part of the box
     * @param [options.lineWidth=50] The width of the confidence interval lines
     * @param [options.notchStyle=null] null=traditional style, 'box' cuts out the whole notch in right angles
     * @param [options.colors=chart default] The color mapping for the notch boxes
     * @returns {*} The chart object
     */
    chart.renderNotchBoxes = function (options) {
        chart.notchBoxes = {};

        //Defaults
        var defaultOptions = {
            show: true,
            showNotchBox: true,
            showLines: false,
            boxWidth: 35,
            medianWidth: 20,
            lineWidth: 50,
            notchStyle: null,
            colors: null
        };
        chart.notchBoxes.options = shallowCopy(defaultOptions);
        for (var option in options) {
            chart.notchBoxes.options[option] = options[option]
        }
        var nOpts = chart.notchBoxes.options;

        //Create notch objects
        for (var cName in chart.groupObjs) {
            chart.groupObjs[cName].notchBox = {};
            chart.groupObjs[cName].notchBox.objs = {};
        }

        /**
         * Makes the svg path string for a notched box
         * @param cNotch Current notch box object
         * @param notchBounds objBound object
         * @returns {string} A string in the proper format for a svg polygon
         */
        function makeNotchBox(cNotch, notchBounds) {
            var scaledValues = [];
            if (nOpts.notchStyle == 'box') {
                scaledValues = [
                    [notchBounds.boxLeft, chart.yScale(cNotch.metrics.quartile1)],
                    [notchBounds.boxLeft, chart.yScale(cNotch.metrics.lowerNotch)],
                    [notchBounds.medianLeft, chart.yScale(cNotch.metrics.lowerNotch)],
                    [notchBounds.medianLeft, chart.yScale(cNotch.metrics.median)],
                    [notchBounds.medianLeft, chart.yScale(cNotch.metrics.upperNotch)],
                    [notchBounds.boxLeft, chart.yScale(cNotch.metrics.upperNotch)],
                    [notchBounds.boxLeft, chart.yScale(cNotch.metrics.quartile3)],
                    [notchBounds.boxRight, chart.yScale(cNotch.metrics.quartile3)],
                    [notchBounds.boxRight, chart.yScale(cNotch.metrics.upperNotch)],
                    [notchBounds.medianRight, chart.yScale(cNotch.metrics.upperNotch)],
                    [notchBounds.medianRight, chart.yScale(cNotch.metrics.median)],
                    [notchBounds.medianRight, chart.yScale(cNotch.metrics.lowerNotch)],
                    [notchBounds.boxRight, chart.yScale(cNotch.metrics.lowerNotch)],
                    [notchBounds.boxRight, chart.yScale(cNotch.metrics.quartile1)]
                ];
            } else {
                scaledValues = [
                    [notchBounds.boxLeft, chart.yScale(cNotch.metrics.quartile1)],
                    [notchBounds.boxLeft, chart.yScale(cNotch.metrics.lowerNotch)],
                    [notchBounds.medianLeft, chart.yScale(cNotch.metrics.median)],
                    [notchBounds.boxLeft, chart.yScale(cNotch.metrics.upperNotch)],
                    [notchBounds.boxLeft, chart.yScale(cNotch.metrics.quartile3)],
                    [notchBounds.boxRight, chart.yScale(cNotch.metrics.quartile3)],
                    [notchBounds.boxRight, chart.yScale(cNotch.metrics.upperNotch)],
                    [notchBounds.medianRight, chart.yScale(cNotch.metrics.median)],
                    [notchBounds.boxRight, chart.yScale(cNotch.metrics.lowerNotch)],
                    [notchBounds.boxRight, chart.yScale(cNotch.metrics.quartile1)]
                ];
            }
            return scaledValues.map(function (d) {
                return [d[0], d[1]].join(",");
            }).join(" ");
        }

        /**
         * Calculate the confidence intervals
         */
        !function calcNotches() {
            var cNotch, modifier;
            for (var cName in chart.groupObjs) {
                cNotch = chart.groupObjs[cName];
                modifier = (1.57 * (cNotch.metrics.iqr / Math.sqrt(cNotch.values.length)));
                cNotch.metrics.upperNotch = cNotch.metrics.median + modifier;
                cNotch.metrics.lowerNotch = cNotch.metrics.median - modifier;
            }
        }();

        /**
         * Take a new set of options and redraw the notch boxes
         * @param updateOptions
         */
        chart.notchBoxes.change = function (updateOptions) {
            if (updateOptions) {
                for (var key in updateOptions) {
                    nOpts[key] = updateOptions[key]
                }
            }

            for (var cName in chart.groupObjs) {
                chart.groupObjs[cName].notchBox.objs.g.remove()
            }
            chart.notchBoxes.prepareNotchBoxes();
            chart.notchBoxes.update();
        };

        chart.notchBoxes.reset = function () {
            chart.notchBoxes.change(defaultOptions)
        };
        chart.notchBoxes.show = function (opts) {
            if (opts !== undefined) {
                opts.show = true;
                if (opts.reset) {
                    chart.notchBoxes.reset()
                }
            } else {
                opts = {show: true};
            }
            chart.notchBoxes.change(opts)
        };
        chart.notchBoxes.hide = function (opts) {
            if (opts !== undefined) {
                opts.show = false;
                if (opts.reset) {
                    chart.notchBoxes.reset()
                }
            } else {
                opts = {show: false};
            }
            chart.notchBoxes.change(opts)
        };

        /**
         * Update the notch box obj values
         */
        chart.notchBoxes.update = function () {
            var cName, cGroup;

            for (cName in chart.groupObjs) {
                cGroup = chart.groupObjs[cName];

                // Get the box size
                var boxBounds = getObjWidth(nOpts.boxWidth, cName);
                var medianBounds = getObjWidth(nOpts.medianWidth, cName);

                var notchBounds = {
                    boxLeft: boxBounds.left,
                    boxRight: boxBounds.right,
                    middle: boxBounds.middle,
                    medianLeft: medianBounds.left,
                    medianRight: medianBounds.right
                };

                // Notch Box
                if (cGroup.notchBox.objs.notch) {
                    cGroup.notchBox.objs.notch
                        .attr("points", makeNotchBox(cGroup, notchBounds));
                }
                if (cGroup.notchBox.objs.upperLine) {
                    var lineBounds = null;
                    if (nOpts.lineWidth) {
                        lineBounds = getObjWidth(nOpts.lineWidth, cName)
                    } else {
                        lineBounds = objBounds
                    }

                    var confidenceLines = {
                        upper: chart.yScale(cGroup.metrics.upperNotch),
                        lower: chart.yScale(cGroup.metrics.lowerNotch)
                    };
                    cGroup.notchBox.objs.upperLine
                        .attr("x1", lineBounds.left)
                        .attr("x2", lineBounds.right)
                        .attr('y1', confidenceLines.upper)
                        .attr("y2", confidenceLines.upper);
                    cGroup.notchBox.objs.lowerLine
                        .attr("x1", lineBounds.left)
                        .attr("x2", lineBounds.right)
                        .attr('y1', confidenceLines.lower)
                        .attr("y2", confidenceLines.lower);
                }
            }
        };

        /**
         * Create the svg elements for the notch boxes
         */
        chart.notchBoxes.prepareNotchBoxes = function () {
            var cName, cNotch;

            if (nOpts && nOpts.colors) {
                chart.notchBoxes.colorFunct = getColorFunct(nOpts.colors);
            } else {
                chart.notchBoxes.colorFunct = chart.colorFunct
            }

            if (nOpts.show == false) {
                return
            }

            for (cName in chart.groupObjs) {
                cNotch = chart.groupObjs[cName].notchBox;

                cNotch.objs.g = chart.groupObjs[cName].g.append("g").attr("class", "notch-plot");

                // Plot Box (default show)
                if (nOpts.showNotchBox) {
                    cNotch.objs.notch = cNotch.objs.g.append("polygon")
                        .attr("class", "notch")
                        .style("fill", chart.notchBoxes.colorFunct(cName))
                        .style("stroke", chart.notchBoxes.colorFunct(cName));
                    //A stroke is added to the notch with the group color, it is
                    // hidden by default and can be shown through css with stroke-width
                }

                //Plot Confidence Lines (default hide)
                if (nOpts.showLines) {
                    cNotch.objs.upperLine = cNotch.objs.g.append("line")
                        .attr("class", "upper confidence line")
                        .style("stroke", chart.notchBoxes.colorFunct(cName));

                    cNotch.objs.lowerLine = cNotch.objs.g.append("line")
                        .attr("class", "lower confidence line")
                        .style("stroke", chart.notchBoxes.colorFunct(cName));
                }
            }
        };
        chart.notchBoxes.prepareNotchBoxes();

        d3.select(window).on('resize.' + chart.selector + '.notchBox', chart.notchBoxes.update);
        chart.notchBoxes.update();
        return chart;
    };

    /**
     * Render a raw data in various forms
     * @param options
     * @param [options.show=true] Toggle the whole plot on and off
     * @param [options.showPlot=false] True or false, show points
     * @param [options.plotType='none'] Options: no scatter = (false or 'none'); scatter points= (true or [amount=% of width (default=10)]); beeswarm points = ('beeswarm')
     * @param [options.pointSize=6] Diameter of the circle in pizels (not the radius)
     * @param [options.showLines=['median']] Can equal any of the metrics lines
     * @param [options.showbeanLines=false] Options: no lines = false
     * @param [options.beanWidth=20] % width
     * @param [options.colors=chart default]
     * @returns {*} The chart object
     *
     */
    chart.renderDataPlots = function (options) {
        chart.dataPlots = {};


        //Defaults
        var defaultOptions = {
            show: true,
            showPlot: false,
            plotType: 'none',
            pointSize: 6,
            showLines: false,//['median'],
            showBeanLines: false,
            beanWidth: 20,
            colors: null,
            padding: 0
        };
        chart.dataPlots.options = shallowCopy(defaultOptions);
        for (var option in options) {
            chart.dataPlots.options[option] = options[option]
        }
        var dOpts = chart.dataPlots.options;

        //Create notch objects
        for (var cName in chart.groupObjs) {
            chart.groupObjs[cName].dataPlots = {};
            chart.groupObjs[cName].dataPlots.objs = {};
        }
        // The lines don't fit into a group bucket so they live under the dataPlot object
        chart.dataPlots.objs = {};

        /**
         * Take updated options and redraw the data plots
         * @param updateOptions
         */
        chart.dataPlots.change = function (updateOptions) {
            if (updateOptions) {
                for (var key in updateOptions) {
                    dOpts[key] = updateOptions[key]
                }
            }

            chart.dataPlots.objs.g.remove();
            for (var cName in chart.groupObjs) {
                chart.groupObjs[cName].dataPlots.objs.g.remove()
            }
            chart.dataPlots.preparePlots();
            chart.dataPlots.update()
        };

        chart.dataPlots.reset = function () {
            chart.dataPlots.change(defaultOptions)
        };
        chart.dataPlots.show = function (opts) {
            if (opts !== undefined) {
                opts.show = true;
                if (opts.reset) {
                    chart.dataPlots.reset()
                }
            } else {
                opts = {show: true};
            }
            chart.dataPlots.change(opts)
        };
        chart.dataPlots.hide = function (opts) {
            if (opts !== undefined) {
                opts.show = false;
                if (opts.reset) {
                    chart.dataPlots.reset()
                }
            } else {
                opts = {show: false};
            }
            chart.dataPlots.change(opts)
        };

        /**
         * Update the data plot obj values
         */
        chart.dataPlots.update = function () {
            var cName, cGroup, cPlot;

            // Metrics lines
            if (chart.dataPlots.objs.g) {
                var halfBand = chart.xScale.rangeBand() / 2; // find the middle of each band
                for (var cMetric in chart.dataPlots.objs.lines) {
                    chart.dataPlots.objs.lines[cMetric].line
                        .x(function (d) {
                            return chart.xScale(d.x) + halfBand
                        });
                    chart.dataPlots.objs.lines[cMetric].g
                        .datum(chart.dataPlots.objs.lines[cMetric].values)
                        .attr('d', chart.dataPlots.objs.lines[cMetric].line);
                }
            }


            for (cName in chart.groupObjs) {
                cGroup = chart.groupObjs[cName];
                cPlot = cGroup.dataPlots;

                if (cPlot.objs.points) {
                    if (dOpts.plotType == 'beeswarm') {
                        var swarmBounds = getObjWidth(100, cName);
                        var yPtScale = chart.yScale.copy()
                            .range([Math.floor(chart.yScale.range()[0] / dOpts.pointSize), 0])
                            .interpolate(d3.interpolateRound)
                            .domain(chart.yScale.domain());
                        var maxWidth = Math.floor(chart.xScale.rangeBand() / dOpts.pointSize);
                        var ptsObj = {};
                        var cYBucket = null;
                        //  Bucket points
                        for (var pt = 0; pt < cGroup.values.length; pt++) {
                            cYBucket = yPtScale(cGroup.values[pt]);
                            if (ptsObj.hasOwnProperty(cYBucket) !== true) {
                                ptsObj[cYBucket] = [];
                            }
                            ptsObj[cYBucket].push(cPlot.objs.points.pts[pt]
                                .attr("cx", swarmBounds.middle)
                                .attr("cy", yPtScale(cGroup.values[pt]) * dOpts.pointSize));
                        }
                        //  Plot buckets
                        var rightMax = Math.min(swarmBounds.right - dOpts.pointSize);
                        for (var row in ptsObj) {
                            var leftMin = swarmBounds.left + (Math.max((maxWidth - ptsObj[row].length) / 2, 0) * dOpts.pointSize);
                            var col = 0;
                            for (pt in ptsObj[row]) {
                                ptsObj[row][pt].attr("cx", Math.min(leftMin + col * dOpts.pointSize, rightMax) + dOpts.pointSize / 2);
                                col++
                            }
                        }
                    } else { // For scatter points and points with no scatter
                        var plotBounds = null,
                            scatterWidth = 0,
                            width = 0;
                        if (dOpts.plotType == 'scatter' || typeof dOpts.plotType == 'number') {
                            //Default scatter percentage is 20% of box width
                            scatterWidth = typeof dOpts.plotType == 'number' ? dOpts.plotType : 20;
                        }

                        plotBounds = getObjWidth(scatterWidth, cName);
                        plotBounds.middle += chart.dataPlots.options.padding
                        plotBounds.right += chart.dataPlots.options.padding
                        plotBounds.left += chart.dataPlots.options.padding
                        width = plotBounds.right - plotBounds.left;

                        for (var pt = 0; pt < cGroup.values.length; pt++) {
                            cPlot.objs.points.pts[pt]
                                .attr("cx", plotBounds.middle + addJitter(true, width))
                                .attr("cy", chart.yScale(cGroup.values[pt]));
                        }
                    }
                }


                if (cPlot.objs.bean) {
                    var beanBounds = getObjWidth(dOpts.beanWidth, cName);
                    for (var pt = 0; pt < cGroup.values.length; pt++) {
                        cPlot.objs.bean.lines[pt]
                            .attr("x1", beanBounds.left)
                            .attr("x2", beanBounds.right)
                            .attr('y1', chart.yScale(cGroup.values[pt]))
                            .attr("y2", chart.yScale(cGroup.values[pt]));
                    }
                }
            }
        };

        /**
         * Create the svg elements for the data plots
         */
        chart.dataPlots.preparePlots = function () {
            var cName, cPlot;

            if (dOpts && dOpts.colors) {
                chart.dataPlots.colorFunct = getColorFunct(dOpts.colors);
            } else {
                chart.dataPlots.colorFunct = chart.colorFunct
            }

            if (dOpts.show == false) {
                return
            }

            // Metrics lines
            chart.dataPlots.objs.g = chart.objs.g.append("g").attr("class", "metrics-lines");
            if (dOpts.showLines && dOpts.showLines.length > 0) {
                chart.dataPlots.objs.lines = {};
                var cMetric;
                for (var line in dOpts.showLines) {
                    cMetric = dOpts.showLines[line];
                    chart.dataPlots.objs.lines[cMetric] = {};
                    chart.dataPlots.objs.lines[cMetric].values = [];
                    for (var cGroup in chart.groupObjs) {
                        chart.dataPlots.objs.lines[cMetric].values.push({
                            x: cGroup,
                            y: chart.groupObjs[cGroup].metrics[cMetric]
                        })
                    }
                    chart.dataPlots.objs.lines[cMetric].line = d3.svg.line()
                        .interpolate("cardinal")
                        .y(function (d) {
                            return chart.yScale(d.y)
                        });
                    chart.dataPlots.objs.lines[cMetric].g = chart.dataPlots.objs.g.append("path")
                        .attr("class", "line " + cMetric)
                        .attr("data-metric", cMetric)
                        .style("fill", 'none')
                        .style("stroke", chart.colorFunct(cMetric));
                }

            }


            for (cName in chart.groupObjs) {

                cPlot = chart.groupObjs[cName].dataPlots;
                cPlot.objs.g = chart.groupObjs[cName].g.append("g").attr("class", "data-plot");

                // Points Plot
                if (dOpts.showPlot) {
                    cPlot.objs.points = {g: null, pts: []};
                    cPlot.objs.points.g = cPlot.objs.g.append("g").attr("class", "points-plot");
                    for (var pt = 0; pt < chart.groupObjs[cName].values.length; pt++) {
                        cPlot.objs.points.pts.push(cPlot.objs.points.g
                            .append("a")
                            .attr("xlink:href", function(d) {
                                return chart.settings["qctype"] + "_" + chart.groupObjs[cName].labels[pt] + "_report.html"
                            })
                            .append("circle")
                            .attr("class", "point")
                            .attr('r', dOpts.pointSize / 2)// Options is diameter, r takes radius so divide by 2
                            .style("fill", chart.dataPlots.colorFunct(cName))
                            .on("mouseover", function() {
                                chart.objs.tooltip
                                    .style("display", null)
                                    .style("left", (d3.event.pageX) + "px")
                                    .style("top", (d3.event.pageY - 28) + "px");
                            })
                            .on("mouseout", function () {
                                chart.objs.tooltip.style("display", "none");
                            })
                            .on("mousemove", pointHover(chart.groupObjs[cName].labels[pt], chart.groupObjs[cName].values[pt]))
                        );
                    }
                }


                // Bean lines
                if (dOpts.showBeanLines) {
                    cPlot.objs.bean = {g: null, lines: []};
                    cPlot.objs.bean.g = cPlot.objs.g.append("g").attr("class", "bean-plot");
                    for (var pt = 0; pt < chart.groupObjs[cName].values.length; pt++) {
                        cPlot.objs.bean.lines.push(cPlot.objs.bean.g.append("line")
                            .attr("class", "bean line")
                            .style("stroke-width", '1')
                            .style("stroke", chart.dataPlots.colorFunct(cName)));
                    }
                }
            }

        };
        chart.dataPlots.preparePlots();

        d3.select(window).on('resize.' + chart.selector + '.dataPlot', chart.dataPlots.update);
        chart.dataPlots.update();
        return chart;
    };

    return chart;
}
