//=================================================================
//  File:		SixSigma.cs
//
//  Namespace:	System.Web.UI.DataVisualization.Charting.Utilities
//
//	Classes:	SixSigma
//
//  Purpose:	To create SixSigma charts.
//
//===================================================================
// Chart Control for ASP.Net
// Copyright ?Microsoft Corporation, all rights reserved
//===================================================================

using System;
using System.Data;
using System.Configuration;
using System.Drawing;
using System.Web.UI.DataVisualization.Charting;

namespace System.Web.UI.DataVisualization.Charting.Utilities
{
    /// <summary>
    /// SixSigma is a utility that includes useful functions related to the Six Sigma strategy.
    /// This utility mainly focuses on those parts of the strategy that involve charting, and producing
    /// those charts.
    /// </summary>
    public class SixSigma
    {
        #region Members
        /// <summary>
        /// sBar holds a value if an schart has been created. sBar is used for a XBAR chart.
        /// </summary>
        private float mysBar = 0;

        /// <summary>
        /// rBar holds a value if an rchart has been created. rBar is used for a XBAR chart.
        /// </summary>
        private float myrBar = 0;

        /// <summary>
        /// Holds the n value passed into sChart or rChart for use by XBAR.
        /// </summary>
        private int mynValue = 0;

        private bool myAutoFitLines;
        private bool myShowLineLabels;
        private Color myLineColor;
        private Color myForeColor;
        private Font myFont;
        #endregion

        #region Properties
        /// <summary>
        /// Controls whether the AxisY will have an automatic Maximum set so that the chart will always
        /// contain the UCL annotation. If left to false, it is up to the user to set a maximum AxisY value
        /// such that all annotations appear on the graph.
        /// </summary>
        public bool AutoFitLines
        {
            get { return myAutoFitLines; }
            set { myAutoFitLines = value; }
        }

        /// <summary>
        /// Controls whether text is added to the lines to label what they are.
        /// </summary>
        public bool ShowLineLabels
        {
            get { return myShowLineLabels; }
            set { myShowLineLabels = value; }
        }

        /// <summary>
        /// Specifies the colour of the line annotations added to each chart.
        /// </summary>
        public Color LineColor
        {
            get { return myLineColor; }
            set { myLineColor = value; }
        }

        /// <summary>
        /// Specifies the colour of the text that will label the lines.
        /// </summary>
        public Color ForeColor
        {
            get { return myForeColor; }
            set { myForeColor = value; }
        }

        /// <summary>
        /// Specifies the font of the text that will label the lines.
        /// </summary>
        public Font Font
        {
            get { return myFont; }
            set { myFont = value; }
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Default Constructor.
        /// </summary>
        public SixSigma()
        {
            myAutoFitLines = false;
            myShowLineLabels = false;
            myLineColor = Color.Red;
            myForeColor = Color.Red;
            myFont = new Font("Arial", 8);
        }
        #endregion

        #region Chart Creation Methods
        /// <summary>
        /// Creates a C-Chart with lines indicating cBar, UCL and LCL. A C-Chart is a measure of the number of
        /// non-conformities per unit, where unit is a fixed rate.
        /// </summary>
        /// <param name="subgroup">An array holding the subgroups. Must aline with the nonconform array.</param>
        /// <param name="nonconform">An array holding the non-conformity data associated with the subgroups. Must aline with the subgroups.</param>
        /// <param name="output">The chart which will have the created series added to it.</param>
        /// <returns>Series which has been created and added to the output chart.</returns>
        public Series cChart(float[] subgroup, float[] nonconform, Chart output)
        {
            //Assure the input all have the same number of items.
            if (subgroup.Length != nonconform.Length)
                throw new ArgumentException("Input arrays must all be the same length.", "subgroup, nonconform");

            //c holds the total number of items (subgroups).
            int c = subgroup.Length;

            //Declare and initialize cBar, LCL and UCL.
            float cbar = 0;
            float LCL = 0;
            float UCL = 0;

            //Calculate cbar.
            for (int i = 0; i < c; i++)
            {
                cbar += (float)(nonconform[i]);
            }
            cbar /= c;

            //Calculate UCL and LCL.
            UCL = (float)(cbar + 3 * System.Math.Sqrt(cbar));
            LCL = (float)(cbar - 3 * System.Math.Sqrt(cbar));

            //If LCL is less than zero, then it should be zero.
            if (LCL < 0)
                LCL = 0;

            //Create the series for the cchart data.
            Series cseries = new Series("cseries");
            //Graph subgroups vs. non-conformities.
            for (int i = 0; i < c; i++)
                cseries.Points.AddXY(subgroup[i], nonconform[i]);

            //Set the series type to a line.
            cseries.ChartType = SeriesChartType.Line;

            //Add the series to the chart.
            output.Series.Add(cseries);

            //Add the lines to the graph as annotations.
            //cBar line.
            addLineAnnotation(output.ChartAreas[cseries.ChartArea].AxisX, output.ChartAreas[cseries.ChartArea].AxisY,
                cseries.Points[0].XValue, cbar, cseries.Points[c - 1].XValue, 0, output);

            //UCL line.
            addLineAnnotation(output.ChartAreas[cseries.ChartArea].AxisX, output.ChartAreas[cseries.ChartArea].AxisY,
                cseries.Points[0].XValue, UCL, cseries.Points[c - 1].XValue, 0, output);

            //LCL line.
            addLineAnnotation(output.ChartAreas[cseries.ChartArea].AxisX, output.ChartAreas[cseries.ChartArea].AxisY,
                cseries.Points[0].XValue, LCL, cseries.Points[c - 1].XValue, 0, output);

            //Add Text Annotations (line labels) if the user desired.
            if (myShowLineLabels)
            {
                addTextAnnotation("CBAR", output.ChartAreas[cseries.ChartArea].AxisX, output.ChartAreas[cseries.ChartArea].AxisY,
                    cseries.Points[0].XValue, cbar, output);

                addTextAnnotation("UCL", output.ChartAreas[cseries.ChartArea].AxisX, output.ChartAreas[cseries.ChartArea].AxisY,
                    cseries.Points[0].XValue, UCL, output);

                addTextAnnotation("LCL", output.ChartAreas[cseries.ChartArea].AxisX, output.ChartAreas[cseries.ChartArea].AxisY,
                    cseries.Points[0].XValue, LCL, output);
            }

            //Scale the chart if the user has requested it.
            FitChart(UCL, cseries.ChartArea, output);

            //Return the series.
            return cseries;
        }

        /// <summary>
        /// Creates a P-Chart with lines indicating pBar, UCL and LCL. A P-Chart is the same as an NP-Chart except
        /// with a variable number of items in each subgroup.
        /// </summary>
        /// <param name="subgroup">An array holding the subgroups. Must aline with the nonconform array and the number tested.</param>
        /// <param name="nonconform">An array holding the non-conformity data associated with the subgroups. Must aline with the subgroups and the number tested.</param>
        /// <param name="numbertested">An array holding the number of items in each subgroup. Must aline with the subgroups and the nonconform array.</param>
        /// <param name="output">The chart which will have the created series added to it.</param>
        /// <returns>Series which has been created and added to the output chart.</returns>
        public Series pChart(float[] subgroup, float[] nonconform, float[] numbertested, Chart output)
        {
            //Assure the input all have the same number of items.
            if ((subgroup.Length != nonconform.Length) || (subgroup.Length != numbertested.Length) || (nonconform.Length != numbertested.Length))
                throw new ArgumentException("Input arrays must all be the same length.", "subgroup, nonconform, numbertested");

            //c holds the total number of items (subgroups).
            int c = subgroup.Length;

            //Declare and initialize phat, UCL, LCL and pBar.
            float[] phat = new float[c];
            float[] UCL = new float[c];
            float[] LCL = new float[c];
            float pbar = 0;

            //Calculate phat(i).
            for (int i = 0; i < c; i++)
                phat[i] = nonconform[i] / numbertested[i];

            //Calculate pbar.
            for (int i = 0; i < c; i++)
            {
                pbar += phat[i];
            }
            pbar /= c;

            //Calculate UCL and LCL.
            for (int i = 0; i < c; i++)
            {
                UCL[i] = (float)(pbar + 3 * System.Math.Sqrt((pbar * (1 - pbar)) / numbertested[i]));
                LCL[i] = (float)(pbar - 3 * System.Math.Sqrt((pbar * (1 - pbar)) / numbertested[i]));
            }

            //Create the series for the pchart data.
            Series pseries = new Series("pseries");
            //Graph subgroup vs. proportion.
            for (int i = 0; i < c; i++)
                pseries.Points.AddXY(subgroup[i], phat[i]);

            //Set the series type to a line.
            pseries.ChartType = SeriesChartType.Line;

            //Add the series to the chart.
            output.Series.Add(pseries);

            //Add the lines to the graph as annotations.
            //PBAR line.
            addLineAnnotation(output.ChartAreas[pseries.ChartArea].AxisX, output.ChartAreas[pseries.ChartArea].AxisY,
                pseries.Points[0].XValue, pbar, pseries.Points[c - 1].XValue, 0, output);

            //Add the UCL and LCL line segments as individual line annotations.
            for (int i = 0; i < c - 1; i++)
            {
                //UCL line.
                addLineAnnotation(output.ChartAreas[pseries.ChartArea].AxisX, output.ChartAreas[pseries.ChartArea].AxisY,
                    subgroup[i], UCL[i], subgroup[i + 1] - subgroup[i], UCL[i + 1] - UCL[i], output);

                //LCL line.
                addLineAnnotation(output.ChartAreas[pseries.ChartArea].AxisX, output.ChartAreas[pseries.ChartArea].AxisY,
                    subgroup[i], LCL[i], subgroup[i + 1] - subgroup[i], LCL[i + 1] - LCL[i], output);
            }

            //Add Text Annotations (line labels) if the user desired.
            if (myShowLineLabels)
            {
                addTextAnnotation("PBAR", output.ChartAreas[pseries.ChartArea].AxisX, output.ChartAreas[pseries.ChartArea].AxisY,
                    pseries.Points[0].XValue, pbar, output);

                addTextAnnotation("UCL", output.ChartAreas[pseries.ChartArea].AxisX, output.ChartAreas[pseries.ChartArea].AxisY,
                    pseries.Points[0].XValue, UCL[0], output);

                addTextAnnotation("LCL", output.ChartAreas[pseries.ChartArea].AxisX, output.ChartAreas[pseries.ChartArea].AxisY,
                    pseries.Points[0].XValue, LCL[0], output);
            }

            //Find the maximum UCL value and store the index of it.
            int maxUCL = 0;
            for (int i = 1; i < c; i++)
                if (UCL[maxUCL] < UCL[i])
                    maxUCL = i;

            //Scale the chart if the user has requested it.
            FitChart(UCL[maxUCL], pseries.ChartArea, output);

            //Return the series.
            return pseries;
        }

        /// <summary>
        /// Creates an NP-Chart with lines indicating CL, UCL and LCL. An NP chart is the same as a P-chart except
        /// that the number of items in each subgroup is the same.
        /// </summary>
        /// <param name="subgroup">An array holding the subgroups. Must aline with the nonconform array.</param>
        /// <param name="nonconform">An array holding the non-conformity data associated with the subgroups. Must aline with the subgroups.</param>
        /// <param name="numbertested">An array holding the number of items in each subgroup. Must aline with the subgroups and the nonconform array.</param>
        /// <param name="output">The chart which will have the created series added to it.</param>
        /// <returns>Series which has been created and added to the output chart.</returns>
        public Series npChart(float[] subgroup, float[] nonconform, int numbertested, Chart output)
        {
            //Assure the input all have the same number of items.
            if (subgroup.Length != nonconform.Length)
                throw new ArgumentException("Input arrays must all be the same length.", "subgroup, nonconform, numbertested");

            //c holds the total number of items (subgroups).
            int c = subgroup.Length;

            //Declare and initialize UCL, LCL and pBar.
            float UCL = 0;
            float LCL = 0;
            float pbar = 0;

            //Calculate pbar.
            for (int i = 0; i < c; i++)
            {
                pbar += nonconform[i];
            }
            pbar /= (c * numbertested);

            //Calculate UCL and LCL.
            UCL = (float)(numbertested * pbar + 3 * System.Math.Sqrt(numbertested * pbar * (1 - pbar)));
            LCL = (float)(numbertested * pbar - 3 * System.Math.Sqrt(numbertested * pbar * (1 - pbar)));

            //If LCL is less than zero, then it should be zero.
            if (LCL < 0)
                LCL = 0;

            //Create the npseries
            Series npseries = new Series("npseries");
            //Graph subgroup vs. non-conforming values.
            for (int i = 0; i < c; i++)
                npseries.Points.AddXY(subgroup[i], nonconform[i]);

            //Set the type to line series.
            npseries.ChartType = SeriesChartType.Line;

            //Add the series to the chart.
            output.Series.Add(npseries);

            //Add the lines to the graph as annotations.
            //CL line.
            addLineAnnotation(output.ChartAreas[npseries.ChartArea].AxisX, output.ChartAreas[npseries.ChartArea].AxisY,
                npseries.Points[0].XValue, pbar * 100, npseries.Points[c - 1].XValue, 0, output);

            //UCL line.
            addLineAnnotation(output.ChartAreas[npseries.ChartArea].AxisX, output.ChartAreas[npseries.ChartArea].AxisY,
                npseries.Points[0].XValue, UCL, npseries.Points[c - 1].XValue, 0, output);

            //LCL line.
            addLineAnnotation(output.ChartAreas[npseries.ChartArea].AxisX, output.ChartAreas[npseries.ChartArea].AxisY,
                npseries.Points[0].XValue, LCL, npseries.Points[c - 1].XValue, 0, output);

            //Add Text Annotations (line labels) if the user desired.
            if (myShowLineLabels)
            {
                addTextAnnotation("PBAR", output.ChartAreas[npseries.ChartArea].AxisX, output.ChartAreas[npseries.ChartArea].AxisY,
                    npseries.Points[0].XValue, pbar * 100, output);

                addTextAnnotation("UCL", output.ChartAreas[npseries.ChartArea].AxisX, output.ChartAreas[npseries.ChartArea].AxisY,
                    npseries.Points[0].XValue, UCL, output);

                addTextAnnotation("LCL", output.ChartAreas[npseries.ChartArea].AxisX, output.ChartAreas[npseries.ChartArea].AxisY,
                    npseries.Points[0].XValue, LCL, output);
            }

            //Scale the chart if the user has requested it.
            FitChart(UCL, npseries.ChartArea, output);

            //Return the series.
            return npseries;
        }

        /// <summary>
        /// Creates a U-Chart with UBar, UCL and LCL lines. A U-Chart is used when the desired chart
        /// is that of the number of non-conformities per inspection unit, where the inspection unit is variable size.
        /// </summary>
        /// <param name="subgroup">An array holding the subgroups. Must aline with the nonconform array.</param>
        /// <param name="nonconform">An array holding the non-conformity data associated with the subgroups. Must aline with the subgroups.</param>
        /// <param name="numbertested">The number of items in each subgroup.</param>
        /// <param name="output">The chart which will have the created series added to it.</param>
        /// <returns>Series which has been created and added to the output chart.</returns>
        public Series uChart(float[] subgroup, float[] nonconform, float[] numbertested, Chart output)
        {
            //Assure the input all have the same number of items.
            if ((subgroup.Length != nonconform.Length) || (subgroup.Length != numbertested.Length) || (nonconform.Length != numbertested.Length))
                throw new ArgumentException("Input arrays must all be the same length.", "subgroup, nonconform, numbertested");

            //c holds the total number of items (subgroups).
            int c = subgroup.Length;

            //Declare and initialize UCL, LCL and uBar.
            float[] UCL = new float[c];
            float[] LCL = new float[c];
            float ubar = 0;

            //Initialize temporary variables to hold temp sums for the ubar calculation
            float sumNumerator = 0;
            float sumDenominator = 0;

            //Calculate ubar.
            for (int i = 0; i < c; i++)
            {
                sumNumerator += nonconform[i];
                sumDenominator += numbertested[i];
            }
            ubar = sumNumerator / sumDenominator;

            //Calculate the Yvalues
            float[] yvalues = new float[c];
            for (int i = 0; i < c; i++)
                yvalues[i] = nonconform[i] / numbertested[i];

            //Calculate UCL and LCL.
            for (int i = 0; i < c; i++)
            {
                UCL[i] = (float)(ubar + 3 * System.Math.Sqrt(ubar / numbertested[i]));
                LCL[i] = (float)(ubar - 3 * System.Math.Sqrt(ubar / numbertested[i]));
            }

            //Create the series for the pchart data.
            Series useries = new Series("useries");
            //Graph subgroup vs. proportion.
            for (int i = 0; i < c; i++)
                useries.Points.AddXY(subgroup[i], yvalues[i]);

            //Set the series type to a line.
            useries.ChartType = SeriesChartType.Line;

            //Add the series to the chart.
            output.Series.Add(useries);

            //Add the lines to the graph as annotations.
            //UBAR line.
            addLineAnnotation(output.ChartAreas[useries.ChartArea].AxisX, output.ChartAreas[useries.ChartArea].AxisY,
                useries.Points[0].XValue, ubar, useries.Points[c - 1].XValue, 0, output);

            //Add the UCL and LCL line segments as individual line annotations.
            for (int i = 0; i < c - 1; i++)
            {
                //UCL line.
                addLineAnnotation(output.ChartAreas[useries.ChartArea].AxisX, output.ChartAreas[useries.ChartArea].AxisY,
                    subgroup[i], UCL[i], subgroup[i + 1] - subgroup[i], UCL[i + 1] - UCL[i], output);

                //LCL line.
                addLineAnnotation(output.ChartAreas[useries.ChartArea].AxisX, output.ChartAreas[useries.ChartArea].AxisY,
                    subgroup[i], LCL[i], subgroup[i + 1] - subgroup[i], LCL[i + 1] - LCL[i], output);
            }

            //Add Text Annotations (line labels) if the user desired.
            if (myShowLineLabels)
            {
                addTextAnnotation("UBAR", output.ChartAreas[useries.ChartArea].AxisX, output.ChartAreas[useries.ChartArea].AxisY,
                    useries.Points[0].XValue, ubar, output);

                addTextAnnotation("UCL", output.ChartAreas[useries.ChartArea].AxisX, output.ChartAreas[useries.ChartArea].AxisY,
                    useries.Points[0].XValue, UCL[0], output);

                addTextAnnotation("LCL", output.ChartAreas[useries.ChartArea].AxisX, output.ChartAreas[useries.ChartArea].AxisY,
                    useries.Points[0].XValue, LCL[0], output);
            }

            //Find the maximum UCL value and store the index of it.
            int maxUCL = 0;
            for (int i = 1; i < c; i++)
                if (UCL[maxUCL] < UCL[i])
                    maxUCL = i;

            //Scale the chart if the user has requested it.
            FitChart(UCL[maxUCL], useries.ChartArea, output);

            //Return the series.
            return useries;
        }

        /// <summary>
        /// Creates a Run Chart of the data and adds an average line. A run chart is a plot of the data without
        /// manipulation along with a line indicating where the average of the points is.
        /// </summary>
        /// <param name="subgroup">An array holding the subgroups. Must aline with the data array.</param>
        /// <param name="data">An array holding the data. Must aline with the subgroup array.</param>
        /// <param name="output">The chart which will have the created series added to it.</param>
        /// <returns>Series which has been created and added to the output chart.</returns>
        public Series runChart(float[] subgroup, float[] data, Chart output)
        {
            //Assure the input all have the same number of items.
            if (subgroup.Length != data.Length)
                throw new ArgumentException("Input arrays must all be the same length.", "subgroup, data");

            //c holds the total number of items (subgroups).
            int c = subgroup.Length;

            //Declare and initialize a variable to hold the average.
            float average = 0;

            //Create the series for the runchart data.
            Series runseries = new Series("runseries");
            //Graph subgroups vs. data.
            for (int i = 0; i < c; i++)
                runseries.Points.AddXY(subgroup[i], data[i]);

            //Set the series type to a line.
            runseries.ChartType = SeriesChartType.Line;

            //Add the series to the chart.
            output.Series.Add(runseries);

            //Calculate the average.
            average = (float)output.DataManipulator.Statistics.Mean("runseries");

            //Add the lines to the graph as annotations.
            //average line.
            addLineAnnotation(output.ChartAreas[runseries.ChartArea].AxisX, output.ChartAreas[runseries.ChartArea].AxisY,
                runseries.Points[0].XValue, average, runseries.Points[c - 1].XValue, 0, output);

            //Return the series.
            return runseries;
        }

        /// <summary>
        /// Creates an S-Chart of prepared data that can be evaluated. If the data is deemed to be in statistical control
        /// an XBAR chart can be created via XBARChart function.
        /// </summary>
        /// <param name="subgroup">An array holding the subgroups. Must aline with the data array. There must be at least 20 subgroups.</param>
        /// <param name="data">Array holding the Standard Deviation of each subgroup.</param>
        /// <param name="n">Number of measurements per subgroup. Must be between 2 and 9.</param>
        /// <param name="processStdDev">Variable returning the process standard deviation estimation.</param>
        /// <param name="output">The chart which will have the created series added to it.</param>
        /// <returns>Series which has been created and added to the output chart.</returns>
        public Series sChart(float[] subgroup, float[] data, int n, out float processStdDev, Chart output)
        {
            //Assure the input all have the same number of items.
            if (subgroup.Length != data.Length)
                throw new ArgumentException("Input arrays must all be the same length.", "subgroup, data");

            //Assure there are at least 20 subgroups
            if (subgroup.Length < 20)
                throw new ArgumentOutOfRangeException("There must be at least 20 subgroups.", "subgroup");

            //Assure n is between 2 and 9
            if (n < 2 || n > 9)
                throw new ArgumentOutOfRangeException("n must be between 1 and 9.", "n");

            //c holds the total number of items (subgroups).
            int c = subgroup.Length;

            //Declare and initialize a variable to hold sbar, UCL and LCL.
            float sbar = 0;
            float UCL = 0;
            float LCL = 0;

            //Declare the constants needed for calculations.
            float[] B3 ={ 0, 0, 0, 0, 0.03F, 0.118F, 0.185F, 0.239F };
            float[] B4 ={ 3.267F, 2.568F, 2.266F, 2.089F, 1.970F, 1.882F, 1.815F, 1.761F };
            float[] C4 ={ 0.7979F, 0.8862F, 0.9213F, 0.9400F, 0.9515F, 0.9594F, 0.9650F, 0.9693F };
            
            //Calculate sbar which is the center line of the standard deviation of each subgroup.
            for (int i = 0; i < c; i++)
                sbar += data[i];
            sbar /= c;

            //Calculate UCL and LCL.
            //It is n-2 as the number is between 2 and 9, but the array starts at 0.
            UCL = B4[n - 2] * sbar;
            LCL = B3[n - 2] * sbar;

            //If LCL is less than zero, then it should be zero.
            if (LCL < 0)
                LCL = 0;

            //Create the series for the sChart data.
            Series sseries = new Series("sseries");
            //Graph subgroups vs. data.
            for (int i = 0; i < c; i++)
                sseries.Points.AddXY(subgroup[i], data[i]);

            //Set the series type to a line.
            sseries.ChartType = SeriesChartType.Line;

            //Add the series to the chart.
            output.Series.Add(sseries);

            //Add the lines to the graph as annotations.
            //sbar line.
            addLineAnnotation(output.ChartAreas[sseries.ChartArea].AxisX, output.ChartAreas[sseries.ChartArea].AxisY,
                sseries.Points[0].XValue, sbar, sseries.Points[c - 1].XValue, 0, output);

            //UCL line.
            addLineAnnotation(output.ChartAreas[sseries.ChartArea].AxisX, output.ChartAreas[sseries.ChartArea].AxisY,
                sseries.Points[0].XValue, UCL, sseries.Points[c - 1].XValue, 0, output);

            //LCL line.
            addLineAnnotation(output.ChartAreas[sseries.ChartArea].AxisX, output.ChartAreas[sseries.ChartArea].AxisY,
                sseries.Points[0].XValue, LCL, sseries.Points[c - 1].XValue, 0, output);

            //Add Text Annotations (line labels) if the user desired.
            if (myShowLineLabels)
            {
                addTextAnnotation("SBAR", output.ChartAreas[sseries.ChartArea].AxisX, output.ChartAreas[sseries.ChartArea].AxisY,
                    sseries.Points[0].XValue, sbar, output);

                addTextAnnotation("UCL", output.ChartAreas[sseries.ChartArea].AxisX, output.ChartAreas[sseries.ChartArea].AxisY,
                    sseries.Points[0].XValue, UCL, output);

                addTextAnnotation("LCL", output.ChartAreas[sseries.ChartArea].AxisX, output.ChartAreas[sseries.ChartArea].AxisY,
                    sseries.Points[0].XValue, LCL, output);
            }

            //Scale the chart if the user has requested it.
            FitChart(UCL, sseries.ChartArea, output);

            //Calculate process standard deviation.
            processStdDev = (float)((sbar / C4[n - 2]) * System.Math.Sqrt(1 - System.Math.Pow(C4[n - 2], 2)));

            //Set the sBar class variable and clear rBar.
            this.mysBar = sbar;
            this.myrBar = 0;

            //Store the n value for use by XBAR.
            this.mynValue = n;

            //Return the series.
            return sseries;
        }

        /// <summary>
        /// Creates an R-Chart of prepared data that can be evaluated. If the data is deemed to be in statistical control
        /// an XBAR chart can be created via XBARChart function.
        /// </summary>
        /// <param name="subgroup">An array holding the subgroups. Must aline with the data array. There must be at least 20 subgroups.</param>
        /// <param name="data">Array holding the Range (biggest value - smallest value) of each subgroup.</param>
        /// <param name="n">Number of measurements per subgroup. Must be between 2 and 9.</param>
        /// <param name="processStdDev">Variable returning the process standard deviation estimation.</param>
        /// <param name="output">The chart which will have the created series added to it.</param>
        /// <returns>Series which has been created and added to the output chart.</returns>
        public Series rChart(float[] subgroup, float[] data, int n, out float processStdDev, Chart output)
        {
            //Assure the input all have the same number of items.
            if (subgroup.Length != data.Length)
                throw new ArgumentException("Input arrays must all be the same length.", "subgroup, data");

            //Assure there are at least 20 subgroups
            if (subgroup.Length < 20)
                throw new ArgumentOutOfRangeException("There must be at least 20 subgroups.", "subgroup");

            //Assure n is between 2 and 9
            if (n < 2 || n > 9)
                throw new ArgumentOutOfRangeException("n must be between 1 and 9.", "n");

            //c holds the total number of items (subgroups).
            int c = subgroup.Length;

            //Declare and initialize a variable to hold sbar, UCL and LCL.
            float rbar = 0;
            float UCL = 0;
            float LCL = 0;

            //Declare the constants needed for calculations.
            float[] D3 ={ 0, 0, 0, 0, 0, 0.076F, 0.136F, 0.184F };
            float[] D4 ={ 3.267F, 2.574F, 2.282F, 2.114F, 2.004F, 1.924F, 1.864F, 1.816F };
            float[] D2 ={ 1.128F, 1.693F, 2.059F, 2.326F, 2.534F, 2.704F, 2.847F, 2.970F };

            //Calculate rbar which is the center line of the range of each subgroup.
            for (int i = 0; i < c; i++)
                rbar += data[i];
            rbar /= c;

            //Calculate UCL and LCL.
            //It is n-2 as the number is between 2 and 9, but the array starts at 0.
            UCL = D4[n - 2] * rbar;
            LCL = D3[n - 2] * rbar;

            //If LCL is less than zero, then it should be zero.
            if (LCL < 0)
                LCL = 0;

            //Create the series for the sChart data.
            Series rseries = new Series("rseries");
            //Graph subgroups vs. data.
            for (int i = 0; i < c; i++)
                rseries.Points.AddXY(subgroup[i], data[i]);

            //Set the series type to a line.
            rseries.ChartType = SeriesChartType.Line;

            //Add the series to the chart.
            output.Series.Add(rseries);

            //Add the lines to the graph as annotations.
            //sbar line.
            addLineAnnotation(output.ChartAreas[rseries.ChartArea].AxisX, output.ChartAreas[rseries.ChartArea].AxisY,
                rseries.Points[0].XValue, rbar, rseries.Points[c - 1].XValue, 0, output);

            //UCL line.
            addLineAnnotation(output.ChartAreas[rseries.ChartArea].AxisX, output.ChartAreas[rseries.ChartArea].AxisY,
                rseries.Points[0].XValue, UCL, rseries.Points[c - 1].XValue, 0, output);

            //LCL line.
            addLineAnnotation(output.ChartAreas[rseries.ChartArea].AxisX, output.ChartAreas[rseries.ChartArea].AxisY,
                rseries.Points[0].XValue, LCL, rseries.Points[c - 1].XValue, 0, output);

            //Add Text Annotations (line labels) if the user desired.
            if (myShowLineLabels)
            {
                addTextAnnotation("RBAR", output.ChartAreas[rseries.ChartArea].AxisX, output.ChartAreas[rseries.ChartArea].AxisY,
                    rseries.Points[0].XValue, rbar, output);

                addTextAnnotation("UCL", output.ChartAreas[rseries.ChartArea].AxisX, output.ChartAreas[rseries.ChartArea].AxisY,
                    rseries.Points[0].XValue, UCL, output);

                addTextAnnotation("LCL", output.ChartAreas[rseries.ChartArea].AxisX, output.ChartAreas[rseries.ChartArea].AxisY,
                    rseries.Points[0].XValue, LCL, output);
            }

            //Scale the chart if the user has requested it.
            FitChart(UCL, rseries.ChartArea, output);

            //Calculate process standard deviation.
            processStdDev = (float)(rbar / D2[n - 2]);

            //Set the rBar class variable and clear sBar.
            this.myrBar = rbar;
            this.mysBar = 0;

            //Store the n value for use by XBAR.
            this.mynValue = n;

            //Return the series.
            return rseries;
        }

        /// <summary>
        /// Creates an XBAR chart of the prepared data. sChart or rChart must have already been called to create a XBAR chart.
        /// </summary>
        /// <param name="subgroup">An array holding the subgroups. Must aline with the data array.</param>
        /// <param name="data">Array holding the Mean of each subgroup. Must aline with the subgroup array.</param>
        /// <param name="output">The chart which will have the created series added to it.</param>
        /// <returns>Series which has been created and added to the output chart.</returns>
        public Series XBARChart(float[] subgroup, float[] data, Chart output)
        {
            //Call the private function with no MRBAR to graph a separate XBAR chart.
            return XBARChartImplementation(subgroup, data, output, 0);
        }

        /// <summary>
        /// Creates an Individuals Chart of the data and places the MRBar, XBAR, UCL and LCL upon it.
        /// </summary>
        /// <param name="subgroup">An array holding the subgroups. Must aline with the data array.</param>
        /// <param name="data">An array holding the data. Must aline with the subgroup array.</param>
        /// <param name="processStdDev">Variable returning the process standard deviation estimation.</param>
        /// <param name="output">The chart which will have the created series added to it.</param>
        /// <returns>Series which has been created and added to the output chart.</returns>
        public Series individualsChart(float[] subgroup, float[] data, out float processStdDev, Chart output)
        {
            //Assure the input all have the same number of items.
            if (subgroup.Length != data.Length)
                throw new ArgumentException("Input arrays must all be the same length.", "subgroup, data");

            //c holds the total number of items (subgroups).
            int c = subgroup.Length;

            //Declare and initialize MRBAR.
            float mrbar = 0;

            //Calculate cbar.
            for (int i = 0; i < c-1; i++)
            {
                mrbar += (float)System.Math.Abs(data[i + 1] - data[i]);
            }
            mrbar /= c - 1;

            //Calculate the process standard deviation.
            processStdDev = (float)(mrbar / 1.128);

            //Return the series
            return XBARChartImplementation(subgroup, data, output, mrbar);
        }

        /// <summary>
        /// Creates an XBAR chart of the prepared data. This function contains the implementation for the public function with one extra
        /// parameter for MRBAR. If MRBAR is specified, then only one chart is created with all the values.
        /// </summary>
        /// <param name="subgroup">An array holding the subgroups. Must aline with the data array.</param>
        /// <param name="data">Array holding the Mean of each subgroup. Must aline with the subgroup array.</param>
        /// <param name="output">The chart which will have the created series added to it.</param>
        /// <param name="mrbar">The value of MRBAR if it has been calculated by Individuals Chart.</param>
        /// <returns>Series which has been created and added to the output chart.</returns>
        private Series XBARChartImplementation(float[] subgroup, float[] data, Chart output, float mrbar)
        {
            //Assure the input all have the same number of items.
            if (subgroup.Length != data.Length)
                throw new ArgumentException("Input arrays must all be the same length.", "subgroup, nonconform, numbertested");

            //Assure that either rBar or sBar have a value, and n has a value. If MRBAR has been specified, do not check.
            if (mrbar == 0)
                if ((this.myrBar == 0 && this.mysBar == 0) || (this.mynValue < 2 || this.mynValue > 9))
                    throw new ArgumentException("sChart or rChart must be called before XBAR.");

            //c holds the total number of items (subgroups).
            int c = subgroup.Length;

            //Declare and initialize a variable to hold sbar, UCL and LCL.
            float xbar = 0;
            float UCL = 0;
            float LCL = 0;

            //Declare the constants needed for calculations.
            float[] A2 ={ 1.880F, 1.023F, 0.729F, 0.577F, 0.483F, 0.419F, 0.373F, 0.337F };
            float[] A3 ={ 2.659F, 1.954F, 1.628F, 1.427F, 1.287F, 1.182F, 1.099F, 1.032F };

            //Calculate xbar, which is the mean of all the subgroup means.
            for (int i = 0; i < c; i++)
                xbar += data[i];
            xbar /= c;

            //Calculate UCL and LCL.
            //Switch based on if we're calculating from a rChart or a sChart, or if a MRBAR was given.
            //It is n-2 as the number is between 2 and 9, but the array starts at 0
            if (mrbar != 0)
            {
                UCL = (float)(xbar + 2.66 * mrbar);
                LCL = (float)(xbar - 2.66 * mrbar);
            }
            else if (this.mysBar != 0)
            {
                UCL = xbar + A3[this.mynValue - 2] * this.mysBar;
                LCL = xbar - A3[this.mynValue - 2] * this.mysBar;
            }
            else if (this.myrBar != 0)
            {
                UCL = xbar + A2[this.mynValue - 2] * this.myrBar;
                LCL = xbar - A2[this.mynValue - 2] * this.myrBar;
            }

            //If LCL is less than zero, then it should be zero.
            if (LCL < 0)
                LCL = 0;

            //Create the series for the xbar data.
            Series xbarseries = new Series("xbarseries");
            //Graph subgroups vs. data.
            for (int i = 0; i < c; i++)
                xbarseries.Points.AddXY(subgroup[i], data[i]);

            //Set the series type to a line.
            xbarseries.ChartType = SeriesChartType.Line;

            //Add the series to the chart.
            output.Series.Add(xbarseries);

            //Add the lines to the graph as annotations.
            //xbar line.
            addLineAnnotation(output.ChartAreas[xbarseries.ChartArea].AxisX, output.ChartAreas[xbarseries.ChartArea].AxisY,
                xbarseries.Points[0].XValue, xbar, xbarseries.Points[c - 1].XValue, 0, output);

            //UCL line.
            addLineAnnotation(output.ChartAreas[xbarseries.ChartArea].AxisX, output.ChartAreas[xbarseries.ChartArea].AxisY,
                xbarseries.Points[0].XValue, UCL, xbarseries.Points[c - 1].XValue, 0, output);

            //LCL line.
            addLineAnnotation(output.ChartAreas[xbarseries.ChartArea].AxisX, output.ChartAreas[xbarseries.ChartArea].AxisY,
                xbarseries.Points[0].XValue, LCL, xbarseries.Points[c - 1].XValue, 0, output);

            //MBAR line if it has been specified
            if (mrbar != 0)
                addLineAnnotation(output.ChartAreas[xbarseries.ChartArea].AxisX, output.ChartAreas[xbarseries.ChartArea].AxisY,
                    xbarseries.Points[0].XValue, mrbar, xbarseries.Points[c - 1].XValue, 0, output);

            //Add Text Annotations (line labels) if the user desired.
            if (myShowLineLabels)
            {
                addTextAnnotation("XBAR", output.ChartAreas[xbarseries.ChartArea].AxisX, output.ChartAreas[xbarseries.ChartArea].AxisY,
                    xbarseries.Points[0].XValue, xbar, output);

                addTextAnnotation("UCL", output.ChartAreas[xbarseries.ChartArea].AxisX, output.ChartAreas[xbarseries.ChartArea].AxisY,
                    xbarseries.Points[0].XValue, UCL, output);

                addTextAnnotation("LCL", output.ChartAreas[xbarseries.ChartArea].AxisX, output.ChartAreas[xbarseries.ChartArea].AxisY,
                    xbarseries.Points[0].XValue, LCL, output);

                if (mrbar!=0)
                    addTextAnnotation("MRBAR", output.ChartAreas[xbarseries.ChartArea].AxisX, output.ChartAreas[xbarseries.ChartArea].AxisY,
                        xbarseries.Points[0].XValue, mrbar, output);
            }

            //Scale the chart if the user has requested it.
            FitChart(UCL, xbarseries.ChartArea, output);
            
            //Return the series.
            return xbarseries;
        }
        #endregion

        #region Math Methods
        /// <summary>
        /// Calculates the mean of an array of numbers.
        /// </summary>
        /// <param name="data">The input array of items of which the mean will be calculated.</param>
        /// <returns>The mean of the data.</returns>
        public float Mean(float[] data)
        {
            //Create a temporary variable to hold the sum.
            float sum = 0;

            //Calculate the sum of the data items.
            for (int i = 0; i < data.Length; i++)
                sum += data[i];

            //Divide them by the total number of items.
            sum /= data.Length;

            //Return the mean.
            return sum;
        }

        /// <summary>
        /// Calculates the range of an array of numbers.
        /// </summary>
        /// <param name="data">The input array of items of which the range will be calculated.</param>
        /// <returns>The range of the data.</returns>
        public float Range(float[] data)
        {
            //Create two variables: one to hold the index of the largest value and one to hold the index of the smallest value.
            int maxIndex = 0;
            int minIndex = 0;

            //Find the largest and smallest values in the array.
            for (int i = 1; i < data.Length; i++)
            {
                if (data[maxIndex] < data[i])
                    maxIndex = i;
                if (data[minIndex] > data[i])
                    minIndex = i;
            }

            //Return the Range.
            return (data[maxIndex] - data[minIndex]);
        }

        /// <summary>
        /// Calculates the standard deviation of an array of numbers.
        /// </summary>
        /// <param name="data">The input array of items of which the standard deviation will be calculated.</param>
        /// <returns>The standard deviation of the data.</returns>
        public float StandardDeviation(float[] data)
        {
            //Create two variables: one to hold the sum of the deviations, and one to hold the mean of the data.
            float sumdeviation = 0;
            float mean = 0;

            //Calculate the mean.
            mean = Mean(data);

            //Calculate the sum of the squared deviations.
            for (int i = 0; i < data.Length; i++)
                sumdeviation += (float)System.Math.Pow(data[i] - mean, 2);

            //Return the standard deviation.
            return (float)System.Math.Sqrt(sumdeviation / (data.Length - 1));
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Scales the chart so that the UCL line will always be within the chart.
        /// </summary>
        /// <param name="UCL">The maximum UCL value.</param>
        /// <param name="ChartArea">The name of the chart area we are plotting to.</param>
        /// <param name="output">The chart which contains the chart area.</param>
        private void FitChart(float UCL, string ChartArea, Chart output)
        {
            //Check to see if scaling is enabled.
            if (myAutoFitLines)
            {
                //Force the chartarea to recalculate the axis values so that we can find the Y-axis maximum.
                output.ChartAreas[ChartArea].RecalculateAxesScale();

                //Check if UCL is outside the maximum Y value.
                if (UCL > (float)output.ChartAreas[ChartArea].AxisY.Maximum)
                    //If so set the Y-axis maximum to be the UCL value.
                    output.ChartAreas[ChartArea].AxisY.Maximum = UCL;
            }
        }

        /// <summary>
        /// Function to add a line annotation to the desired chart in the format which is followed by
        /// all the line annotations in this add-on.
        /// </summary>
        /// <param name="AxisX">X-Axis that the line annotation is to use for co-ordinates.</param>
        /// <param name="AxisY">Y-Axis that the line annotation is to use for co-ordinates.</param>
        /// <param name="X">X value for the start of the line.</param>
        /// <param name="Y">Y value for the start of the line.</param>
        /// <param name="Width">Width of the line.</param>
        /// <param name="Height">Height of the line.</param>
        /// <param name="output">The chart that the line annotation is being added to.</param>
        private void addLineAnnotation(Axis AxisX, Axis AxisY, double X, double Y, double Width, double Height, Chart output)
        {
            //Create a new line annotation.
            LineAnnotation lineAnnotation = new LineAnnotation();

            //Set each property to the parameters passed in.
            lineAnnotation.AxisX = AxisX;
            lineAnnotation.AxisY = AxisY;
            lineAnnotation.Y = Y;
            lineAnnotation.X = X;
            lineAnnotation.Height = Height;
            lineAnnotation.Width = Width;

            //Turn off relative size to get graph-oriented co-ordinates and set the line color.
            lineAnnotation.IsSizeAlwaysRelative = false;
            lineAnnotation.LineColor = myLineColor;

            //Add the annotation to the chart.
            output.Annotations.Add(lineAnnotation);
        }

        /// <summary>
        /// Function to add text as an annotation to the chart.
        /// </summary>
        /// <param name="text">The text that we wish to display.</param>
        /// <param name="AxisX">X-Axis that the text annotation is to use for co-ordinates.</param>
        /// <param name="AxisY">Y-Axis that the text annotation is to use for co-ordinates.</param>
        /// <param name="X">X value for the start of the text.</param>
        /// <param name="Y">Y value for the start of the text.</param>
        /// <param name="output">The chart that the line annotation is being added to.</param>
        private void addTextAnnotation(string text, Axis AxisX, Axis AxisY, double X, double Y, Chart output)
        {
            //Create a new line annotation.
            TextAnnotation textAnnotation = new TextAnnotation();

            //Set each property to the parameters passed in.
            textAnnotation.AxisX = AxisX;
            textAnnotation.AxisY = AxisY;
            textAnnotation.Y = Y;
            textAnnotation.X = X;
            textAnnotation.Text = text;

            //Turn off relative size to get graph-oriented co-ordinates and set the aesthetic properties.
            textAnnotation.IsSizeAlwaysRelative = false;
            textAnnotation.ForeColor = myForeColor;
            textAnnotation.Font = myFont;

            //Add the annotation to the chart.
            output.Annotations.Add(textAnnotation);
        }
        #endregion
    }
}
