In this article, I will show you How to Make a Control Chart with Excel VBA. Control charts are widely used to monitor process stability and control.
The process data are always plotted in order with three lines added:
- A central line for the average,
- An upper line for the upper control limit,
- And a lower line for the lower control limit.
If a process is stable and in control, the process data will fall within control limits. Otherwise, the data will fall out of control limits. By comparing process data against these lines, we can draw conclusions about whether the process variation is in control or is out of control.
Based on the above description, we can see that a control chart can be developed by following 4 steps:
- Draw a series graph
- Add a central line which is a reference line to indicate process location
- Add the other reference lines – upper and lower lines – to show process dispersion
- Customize the chart to make it more beautiful
Let’s create the dummy data
Before diving into the programming world, let’s use the Excel RND function to create random dummy data which will be used later to plot the control chart. Suppose that the random number represents 20 high school students’ height and will fall between 158 and 180. By running the following code, we can get 20 random numbers.
Sub DummyData()
'Populate header
Worksheets(1).Cells(1, 1) = "Student No"
Worksheets(1).Cells(1, 2) = "Height"
'Apply RND function to create random dummy data
For i = 2 To 21
Worksheets(1).Cells(i, 1) = i - 1
Worksheets(1).Cells(i, 2) = Int((180 - 158 + 1) * Rnd + 158)
Next i
End Sub
With the sample data that we just created, we can use the below code to compute mean, LCL and UCL of sample data which will be used to draw the central line, lower line and upper line, respectively. We use a formula to compute statistics so that values of mean, standard deviation, LCL, and UCL can change automatically once we run DummyData to change sample data.
'Get last used row in column B
nlast = Worksheets(1).Cells(Rows.Count, "B").End(xlUp).Row
'Compute Mean
For i = 2 To nlast
Cells(i, 3).Formula = "=Average(" & "B2:B" & nlast & ")"
Next i
'Std
For i = 2 To nlast
Cells(i, 4).Formula = "=StDev(" & "B2:B" & nlast & ")"
Next i
'UCL and LCL
For i = 2 To nlast
'UCL
Cells(i, 5).Formula = "=Average(" & "B2:B" & nlast & ") + StDev(" & "B2:B" & nlast & ")*3"
'LCL
Cells(i, 6).Formula = "=Average(" & "B2:B" & nlast & ") - StDev(" & "B2:B" & nlast & ")*3"
Next i
'Define header
Worksheets(1).Cells(1, 3) = "Mean"
Worksheets(1).Cells(1, 4) = "Std"
Worksheets(1).Cells(1, 5) = "UCL"
Worksheets(1).Cells(1, 6) = "LCL"
Here’s our dummy data
Here shows how the data looks like and data may vary from time to time when running the above code.
Dummy data created by the above code
So far, we have all data essential for control charts and now let’s move on to the most important part – how to draw control charts using VBA programming.
First of all, we need to declare a ChartObject object. The ChartObject object acts as a container for all the elements of a chart. Let’s call it myChtobj but you can use any name. Here display the methods (together with examples) that we will use to manipulate myChtobj object.
chartobjects.Add(Left, Top, Width, Height) [Create a blank, embedded chart on a worksheet or a chart sheet] | |
Argument | Left: The distance between the left edge of the sheet and the right edge of the chart in points |
Top: The distance between the top of the sheet and the top of the chart in points | |
Width: The width of the chart in points | |
Height: The height of the chart in points | |
Chartobjects(Index) [Refer to a single embedded chart or a collection of all the embedded charts] | |
Argument | Index: The name or number of the chart. This argument can be an array, to specify more than one chart |
Chartobjects(Index).HasTitle = True [Add a title to embedded chart] | |
Chartobjects(Index).ChartTitle.Text = “Height of 20 students” [Set or change title of the embedded chart] | |
Chartobjects(Index).SeriesCollection.Add  source:=Worksheets(“Sheet1”).Range(“B2:B21”) [Add a new series in embedded chart] | |
Chartobjects(Index). ChartType = xlLineMarkers [Specifies the chart type. Option – xlLineMarkers – represents a line with data markers and is suitable for control charts. ] |
Creating the control chart
Now let’s try to create elements such as the series graph, central lines, UCL, and LCL lines and put them into the chart container. Chart.SeriesCollection.NewSeries method is available for use. The following shows how to plot a series graph.
Set MyNewSrs = myChtObj.Chart.SeriesCollection.NewSeries
With MyNewSrs
.Name = "Data"
.Values = Worksheets("Sheet1").Range("B2:B21")
End With
By repeating adding new series using above approach, we can get a graph like below.
However, it looks ugly. First of all, we need to remove data markers from the central line, upper line as well as lower line and change the foreground color of the series. Here gives you some methods to change series and markers properties. In order to remove markers, we can just set the value of MarkerStyle as xlNone.
'Data
Set MyNewSrs = myChtObj.Chart.SeriesCollection.NewSeries
With MyNewSrs
.Name = "Data"
.Values = Worksheets("Sheet1").Range("B2:B21")
.Format.Line.Visible = False
.Format.Line.Visible = True
.Format.Line.ForeColor.RGB = RGB(0, 255, 0)
.Format.Line.Weight = 1
.Format.Line.Transparency = 0
.MarkerSize = 3
.MarkerForegroundColor = RGB(0, 255, 0)
.MarkerBackgroundColor = RGB(0, 255, 0)
.MarkerStyle = xlMarkerStyleCircle
End With
Obviously, it is still not beautiful. We need to change legend, axis, and chart itself. There is a little trick when coming to determining y-axis scale. Mod function can be applied to automate the computation of max value and min value (see below in red for details).
Please note that we need to take both source data, UCL and LCL into consideration when trying to compute maximum scale and minimum scale.
'Ajust y-axis Scale
'Get max/min among source data, UCL and LCL
Cells(2, 7).Formula = "=max(" & "B2:B" & nlast & ",E2)"
Cells(1, 7) = "Max"
Cells(2, 8).Formula = "=min(" & "B2:B" & nlast & ",F2)"
Cells(1, 8) = "Min"
With myChtObj.Chart.Axes(xlValue, xlPrimary)
.MaximumScale = Int(Cells(2, 7).Value) + (10 - (Int(Cells(2, 7).Value) Mod 10))
.MinimumScale = Int(Cells(2, 8).Value) - (Int(Cells(2, 8).Value) Mod 10)
'Remove major gridlines
.HasMajorGridlines = False
End With
And I also present the output above. It looks much better. But I still want to delete the legend and insert text next to lines. However, the plot area will become wider than before without legend. Therefore, we need to retrieve the current width of the plot area before removing the legend and then re-size the plot area’s width as before. This can be done by the following code.
'Get current width of plot area
pwidth = myChtObj.Chart.PlotArea.Width
'Remove legend
myChtObj.Chart.Legend.Delete
'Set the width of plot area equal to width of orignal one
myChtObj.Chart.PlotArea.Width = pwidth
'Get current width of plot area
pwidth = myChtObj.Chart.PlotArea.Width
'Remove legend
myChtObj.Chart.Legend.Delete
'Set the width of plot area equal to width of orignal one
myChtObj.Chart.PlotArea.Width = pwidth
Here’s all the code
Here you can find all the code.
Sub ControlChart()
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Part 1 - Calculate '
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Get last used row in column B
nlast = Worksheets(1).Cells(Rows.Count, "B").End(xlUp).Row
'Compute Mean
For i = 2 To nlast
Cells(i, 3).Formula = "=Average(" & "B2:B" & nlast & ")"
Next i
'Std
For i = 2 To nlast
Cells(i, 4).Formula = "=StDev(" & "B2:B" & nlast & ")"
Next i
'UCL and LCL
For i = 2 To nlast
'UCL
Cells(i, 5).Formula = "=Average(" & "B2:B" & nlast & ") + StDev(" & "B2:B" & nlast & ")*3"
'LCL
Cells(i, 6).Formula = "=Average(" & "B2:B" & nlast & ") - StDev(" & "B2:B" & nlast & ")*3"
Next i
'Define header
Worksheets(1).Cells(1, 3) = "Mean"
Worksheets(1).Cells(1, 4) = "Std"
Worksheets(1).Cells(1, 5) = "UCL"
Worksheets(1).Cells(1, 6) = "LCL"
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Part 2 - Chart '
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Define Object
Dim myChtObj As ChartObject
Set myChtObj = ActiveSheet.ChartObjects.Add(Left:=400, Width:=400, Top:=25, Height:=300)
myChtObj.Chart.ChartType = xlLineMarkers
'Data
Set MyNewSrs = myChtObj.Chart.SeriesCollection.NewSeries
With MyNewSrs
.Name = "Data"
.Values = Worksheets("Sheet1").Range("B2:B21")
.Format.Line.Visible = False
.Format.Line.Visible = True
.Format.Line.ForeColor.RGB = RGB(0, 255, 0)
.Format.Line.Weight = 2
.Format.Line.Transparency = 0
.MarkerSize = 3
.MarkerForegroundColor = RGB(0, 255, 0)
.MarkerBackgroundColor = RGB(0, 255, 0)
.MarkerStyle = xlMarkerStyleCircle
End With
'Central line
Set MyNewSrs = myChtObj.Chart.SeriesCollection.NewSeries
With MyNewSrs
.Name = "Mean"
.Values = Worksheets("Sheet1").Range("C2:C21")
.Format.Line.Visible = False
.Format.Line.Visible = True
.Format.Line.ForeColor.RGB = RGB(255, 0, 0)
.MarkerStyle = xlNone
End With
'Upper line
Set MyNewSrs = myChtObj.Chart.SeriesCollection.NewSeries
With MyNewSrs
.Name = "UCL"
.Values = Worksheets("Sheet1").Range("E2:E21")
.Format.Line.Visible = False
.Format.Line.Visible = True
.Format.Line.ForeColor.RGB = RGB(0, 0, 255)
.MarkerStyle = xlNone
End With
'Lower line
Set MyNewSrs = myChtObj.Chart.SeriesCollection.NewSeries
With MyNewSrs
.Name = "LCL"
.Values = Worksheets("Sheet1").Range("F2:F21")
.Format.Line.Visible = False
.Format.Line.Visible = True
.Format.Line.ForeColor.RGB = RGB(0, 0, 255)
.MarkerStyle = xlNone
End With
'Ajust y-axis Scale
'Get max/min among source data, UCL and LCL
Cells(2, 7).Formula = "=max(" & "B2:B" & nlast & ",E2)"
Cells(1, 7) = "Max"
Cells(2, 8).Formula = "=min(" & "B2:B" & nlast & ",F2)"
Cells(1, 8) = "Min"
With myChtObj.Chart.Axes(xlValue, xlPrimary)
.MaximumScale = Int(Cells(2, 7).Value) + (10 - (Int(Cells(2, 7).Value) Mod 10)) + 10
.MinimumScale = Int(Cells(2, 8).Value) - (Int(Cells(2, 8).Value) Mod 10) - 10
'Remove major gridlines
.HasMajorGridlines = False
End With
'Get current width of plot area
pwidth = myChtObj.Chart.PlotArea.Width
'Remove legend
myChtObj.Chart.Legend.Delete
'Set the width of plot area equal to width of orignal one
myChtObj.Chart.PlotArea.Width = pwidth
'Set marker value label for the last marker
Count = nlast - 1
With myChtObj.Chart.SeriesCollection(2).Points(Count)
.HasDataLabel = Ture
.DataLabel.Characters.Text = Worksheets(1).Cells(1, 3)
.DataLabel.Position = xlLabelPositionRight
.DataLabel.Font.Size = 12
.DataLabel.Font.Bold = True
.DataLabel.Font.Color = RGB(255, 0, 0)
End With
For i = 3 To 4
With myChtObj.Chart.SeriesCollection(i).Points(Count)
.HasDataLabel = Ture
.DataLabel.Characters.Text = Worksheets(1).Cells(1, i + 2)
.DataLabel.Position = xlLabelPositionRight
.DataLabel.Font.Size = 12
.DataLabel.Font.Bold = True
.DataLabel.Font.Color = RGB(0, 0, 255)
End With
Next i
End Sub
Reminder
Please note that we need to do an examination before starting plotting data because the data should be normally distributed when using control charts. Otherwise, the chart may signal an unexpectedly high rate of false alarms.
Download the working file
Download the working file from the link below.
Related Articles
- Excel Chart Elements: Parts of Charts in Excel
- How to Create a Combo Excel Chart
- Create Combination Charts with a Secondary Axis in Excel
- How to Create a Dynamic Chart in Excel Using VBA (with Easy Steps)
- Create a Clustered Column Pivot Chart in Excel (with Easy Steps)
- Excel VBA: Get Source Data Range from a Chart (with Quick Steps)
Hi
Very interesting, however, control charts are meant for daily data input…the chart should update automatically….can you present one with daily input and automatic chart update?
I use control charts daily and have problems with this, also Mean Std UCL LCL must be in one cell and not in every entry..
Hi Sew. Thanks for your suggestions. I will write another post later when I am free.
obviously like your web site however you need to check the spelling on several of your posts.
A number of them are rife with spelling problems and I in finding it very
bothersome to inform the reality on the other hand I’ll
surely come back again.
Thanks a lot for your feedback. I will make plans to correct them. Best regards