Excel VBA Good Book
Excel VBA Good Book
Excel VBA Good Book
Excel
October 6, 2002
Contents
1 Introduction 3
1.1 Purpose . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Who should participate . . . . . . . . . . . . . . . . . . . . . . . 3
1.3 Materials used . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.4 Conventions used . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.5 What one fool can do another can . . . . . . . . . . . . . . . . . 4
2 Getting started 4
2.1 What you need to follow the examples in this document . . . . . 4
2.2 The macro recorder - the first macro . . . . . . . . . . . . . . . . 5
2.2.1 What is the macro recorder . . . . . . . . . . . . . . . . . 5
2.2.2 Recording the first macro . . . . . . . . . . . . . . . . . . 5
2.2.3 Recording with absolute and relative references . . . . . . 5
2.2.4 What can be recorded . . . . . . . . . . . . . . . . . . . . 6
2.3 Assigning macros to shortcut keys or buttons . . . . . . . . . . . 6
2.3.1 Assign a macro to a shortcut key . . . . . . . . . . . . . . 6
2.3.2 Assigning macros to menu buttons . . . . . . . . . . . . . 6
2.3.3 Assign macro to a button on a sheet . . . . . . . . . . . . 6
2.4 The Visual Basic Editor . . . . . . . . . . . . . . . . . . . . . . . 7
2.4.1 Activating VBE and a brief overview . . . . . . . . . . . . 7
2.4.2 Taking a first look at the code . . . . . . . . . . . . . . . 7
2.4.3 A few hints for VBE . . . . . . . . . . . . . . . . . . . . . 8
2.5 Does the macro recorder create smooth, efficient code? . . . . . . 9
1
4.3.6 Error handling . . . . . . . . . . . . . . . . . . . . . . . . 24
2
1 Introduction
1.1 Purpose
The purpose of this course is to:
• Demonstrate the use of so-called office programs in research.
• Make the participants comfortable with the use of office programs.
• Introduce tools for automating tedious tasks in research.
• Make the participants comfortable with macro programming using VBA.
In short to help the participants overcome some of the boring, tedious tasks and
free up time for research.
3
Sub MyMacro()
’Macro example
ActiveSheet.Pictures.Insert ( _
"C:\temp\01valve.jpg")
End Sub
Keyboard shortcuts are written in bold face e.g. pressing ALT+F11 activates
the visual basic editor. Menu access is written with arrows indicating the path
of the mouse (or sequence of shortcut keys); to save a file click File→Save.
2 Getting started
2.1 What you need to follow the examples in this docu-
ment
All examples in this document were programmed in Microsoft Visual Basic for
Applications (VBA) to work in Microsoft Office XP (Excel). It is highly unlikely
4
that they will work without modifications in other office suites, but most of them
can be made to work with StarBasic (not for beginners) in the StarOffice suite
and earlier versions of Microsoft Office should do fine too. So to follow the
examples office you should have Microsoft Office (preferably XP) installed on
your system, and you also need the Macro Programming environment Visual
Basic (installed by default), and the Visual Basic Editor (VBE) installed. If you
have a standard installation of MS Office you should be good to go.
and you will not be able to activate it. To correct this choose Tools→Macro→Security. . ., and
set the security level to medium or low. You should restart Excel if the Record new macro
option is not available after this.
5
turn the macro recorder off. If you try to run the macro now, you will notice
that it inserts whatever information you have told it to, but it does it in the
same position as before relative to your current starting point. That is why it
is called relative references. We could write pages about how this works, but
when you play around with it you will notice the difference. When we get more
comfortable with VBA we will be able to explain the different behavior from
the recorded code, and we will appreciate the different macro strategies.
6
2.4 The Visual Basic Editor
Until now you have been creating macros, and you havent written a single line
of code yet. This does not mean that the code has not been written. You have
just had the macro recorder do the work for you. This is also a fine solution for
some problems, but in the long run you need to be able to modify the code made
by the recorder and create your own. The MS Office Suite comes with a built
in programming environment, The Visual Basic Editor (VBE). In this section
we will just browse through some of the main features of VBE. We will not be
able to cover all of them, but we will return to them when it seems appropriate
(We will not cover the object browser in this section, but save it for the section
on object structure).
7
’ Macro recorded 19/04/2000 by Michael Schacht Hansen
’
’
Range("D8").Select
ActiveCell.FormulaR1C1 = "Freedom"
Range("E8").Select
ActiveCell.FormulaR1C1 = "to"
Range("F8").Select
ActiveCell.FormulaR1C1 = "choose"
Range("F9").Select
End Sub
Depending on where you entered your data and what you entered your macro
will look a little bit different, but they should look more or less the same. The
first few lines starting with ”’” are comments and are not interpreted when the
macro is executed, but the remaining lines are. It may seem a bit confusing for
you at first, but you will understand the code completely before long. The macro
starts with the Sub statement followed by the name of the macro. This means a
new macro starts here and it continues on to the End Sub statement. For now
just accept the way things look. If you examine the other macro recorded with
relative references you will find something like this:
Sub Macro2()
’
’ Macro2 Macro
’ Macro recorded 19/04/2000 by Michael Schacht Hansen
’
’
ActiveCell.Select
ActiveCell.FormulaR1C1 = "Freedom"
ActiveCell.Offset(0, 1).Range("A1").Select
ActiveCell.FormulaR1C1 = "to"
ActiveCell.Offset(0, 1).Range("A1").Select
ActiveCell.FormulaR1C1 = "choose"
ActiveCell.Offset(0, 1).Range("A1").Select
End Sub
This also might not make sense to you, but for now just notice that the second
version makes frequent use of the keyword Offset which should tell you that
this version works in relative coordinates from the starting point, while the first
version have absolute cell references hard-coded into the macro. Hence it works
with absolute references.
You should try changing the code for the macro and observe the outcome.
Start by changing the text you recognize as the text or formulas you entered
when recording the macro. Then continue to change some of the cell references
and see what happens.
8
• A macro can easily be executed from VBE, when you have made changes
and want to observe the effect. This is done by placing the cursor some-
where within the code of the macro you want to execute and pressing
F5.
’
With ActiveSheet.PageSetup
.PrintTitleRows = ""
.PrintTitleColumns = ""
End With
ActiveSheet.PageSetup.PrintArea = ""
With ActiveSheet.PageSetup
.LeftHeader = ""
.CenterHeader = ""
.RightHeader = ""
.LeftFooter = ""
.CenterFooter = ""
.RightFooter = ""
.LeftMargin = Application.InchesToPoints(0.75)
.RightMargin = Application.InchesToPoints(0.75)
.TopMargin = Application.InchesToPoints(1)
.BottomMargin = Application.InchesToPoints(1)
9
.HeaderMargin = Application.InchesToPoints(0.5)
.FooterMargin = Application.InchesToPoints(0.5)
.PrintHeadings = False
.PrintGridlines = False
.PrintComments = xlPrintNoComments
.PrintQuality = 600
.CenterHorizontally = False
.CenterVertically = False
.Orientation = xlLandscape
.Draft = False
.PaperSize = xlPaperLetter
.FirstPageNumber = xlAutomatic
.Order = xlDownThenOver
.BlackAndWhite = False
.Zoom = 100
.PrintErrors = xlPrintErrorsDisplayed
End With
End Sub
You would have obtained the same effect with the following macro:
Sub ChangeOrientation()
ActiveSheet.PageSetup.Orientation = xlLandscape
End Sub
Obviously the macro recorder is not very efficient in this case, but on the other
hand we learned how to change the page orientation by recording the macro,
and we were then able to create the compact macro. This is a good illustration
of the strength and weakness of the recorder. It is an excellent learning tool,
but with a little training you are a much better programmer! Aside from this
obvious disadvantage of having the recorder do all the code for you, there is
another. There are a lot of features that you simply cannot record. For instance
try this macro:
Sub DisplayBox()
MsgBox("This is a message to the user")
End Sub
When executed in Excel this should result in a dialog box, with a message to
the user. You have no way of recording actions like that. There are lots of other
(more important) examples, like creating loops that repeat the same action over
and over (do the work for you while you drink coffee). The following chapters
will be a short tour of the basic principles needed to write your own macros
without use of the recorder, but remember to use the macro recorder when you
want to find the command syntax for a specific action in Excel.
10
released today. The reason why VBA is object orientated is probably because
MS Office programs were programmed object orientated, and when creating a
programming language for manipulating these programs it seemed obvious to
apply an object orientated strategy. If you are already comfortable with OOP
and referencing in object structures, you should skip this section, but if you are
not quite sure what OOP is, please read on.
Earth.Countries("Denmark").Cars("XP 38 999").Color
If you were to interpret this statement it would translate into something like:
”the color of the car named XP 38 999 in the country called Denmark on
the planet Earth”. And here we have actually suggested that Earth might not
even be the mother object (it might be part of the collection of Planets in the
Universe. . . part of the collection Universes. . .). Anyway it seems obvious that
the syntax illustrated above is much more useful than actually spelling out what
you want.
Now the purpose of programming was to manipulate the model. We now
have a model of Earth in a computer memory, and we want to observe what
11
happens if we change the color of a specific car to red. The code would look
something like:
Earth.Countries("Denmark").Cars("XP 38 999").Color = Red
If we wanted to make the car move, the code might look like the following. We
are not accessing a property of the object we are asking it so execute a function
(use a method) to manipulate the object. We are also supplying coordinates (for
the movement):
Earth.Countries("Denmark").Cars("XP 38 999").Move(209,99)
This could be a movement of 209 units to the right and 99 units down. This
is actually pretty simple once you get used to it. Programming Excel is just
like manipulating our model of Earth. Excel was build on an object model, and
accessing different part of the program should be pretty easy. VBA is actually
just a tool to access and manipulate the different objects of Excel.
12
Often we are interested in referencing specific cells in a sheet. We have already
seen how cells are referenced in the examples recorded with the macro recorder.
To change the value of a specific cells we would write:
Application.Workbooks("Book1").Sheets(1). _
Range("A1").Value = 68
The above statements is equivalent to instruction somebody to: Set the value of
cell A1 on Sheet 1 in Workbook Book1 to 68. The dot notation syntax makes
it a little more clear, what you actually mean.
Referencing an object or property of an object with a reference starting with
Application. is often referred to as the fully qualified reference. This means that
regardless of the context you state the reference in, it will always mean the
same. Excel can also deal with implicit references. This may or may not sound
complicated, but it is actually quite simple. If you do not state the fully qualified
reference, VBA fills in the missing part of the reference based on the context.
For instance:
Sheets(1).Range("A1").Value = 68
Application.Workbooks("Book1").Sheets(1). _
Range("A1").Value = 68
It may all seem a bit strange to you at the moment. But after working through
a few examples it will all seem more clear.
13
3.3.1 VBA documentation
There is plenty of sources for information on the VBA object model. Just try
typing ”Microsoft Excel Objects” in the search facility of the VBE help files.
One of the listed results should be a graphical tutorial of the objects.
When ever you have typed a VBA keyword in a module, you can place
the cursor in the keyword and press F1. This should bring you to help on that
keyword. Try the word ”Application”. The help screen should give you an option
to list all the methods and properties of the object.
14
4.1 Understanding variables and constants
4.1.1 Definitions
Variables (constants are variables that dont change) are an essential to any
programming language. Many people have an idea of what a variable might be,
and several thousand different definitions have been made to try and make it
clear what a variable is. My definition would be: A variable is an identifier (a
name) for a storage location (part of the computers memory) which holds a
value of some sort. This may sound complicated, but it isnt really. It just means
that you can use the name of the variable to reference a value in the computers
memory, and you can use the same name to change the value of the variable.
Constants are just variables that cannot be changed.
You may choose almost any name for your variables. The rules may differ a
little from programming language to programming language, in VBA you must
follow these rules:
• The name can consist of letters and numbers, but it must start with a
letter.
• You cannot use space or periods in the name.
• VBA variable names are not case sensitive (”MyVar” is the same as ”my-
var”’).
• In general do not include any non-letter or non-number characters.
15
• It makes the code easier to read for others.
• If you always declare variables, VBA will tell you if you have misspelled
a variable. These are the kind of errors it takes hours to find.
• Your macro will take up less memory.
• You code will run faster. You might not think of this as a problem, but
you will be amazed how fast MS Office can run itself into the ground.
• VBA will check if you are putting the right type of data into the variable
VBA has a variety of built in data types. We will just mention a few of the
most widely used here. Look in the help files for a complete list of data types.
Below is a table of the most common data types. Dont worry if it seems a little
confusing.
If you introduce a variable name in your code without any prior declaration of
the variable, VBA will automatically assume that this variable is of type Variant.
This means that it can contain any type of data and VBA will automatically
change the type of the variable when needed. This may sound like a really
good idea. You would not have to worry about data types and VBA would
do the work for you. In reality it is a very bad idea. I have already named a
number of reasons why, but just imagine this. In the beginning of your code,
you do some calculations, and the results end up in a variable. You want to
use this result in a later calculation, but at this later point, you make a typing
error and the name of the variable is misspelled. VBA would think you are
introducing a new variable. Such a new variable would not contain any useful
value and your macro would fail. Errors like that are very hard to find, and
for that reason alone you should always declare your variables. If it is not clear
to you why you should declare your variables, you can either just accept it or
go back and read the above section again. The declaration of variables is done
with the Dim statement. Dim is short for Dimension, dimensioning is another
word for declaring. The following shows examples of variable declaration. The
declarations must be made before the code where the variable is used.
Dim MyNumber As Integer
Dim theFloat As Double
Dim theName As String
16
Once the variable has been declared it can be used in the code, and VBA will
give you an error message if you try to put the wrong type of data into your
variable. Constants are declared with a Const statement:
Const InterestRate As Single = 0.1453
Const Temperature As Integer = 25
You may wonder what constants are good for when you cant change their values.
You might as well hard code the values into the macro. But consider this. You are
building a macro to do some analysis on some measurements. In the calculations
you might use certain constants like temperature. If this value is used many
times it is not a good idea to hard code the value. If you want to use the macro
for another experiment with another temperature, you would have to change
the value in a lot of places and you might forget some of them. Using constants
also make your code more readable.
If you dont tell VBA otherwise, it will allow you to implicitly declare vari-
ables in your code. This means a new variable name in the code will automati-
cally get the type Variant if it has not been declared. As mentioned this is a bad
idea, so to force yourself to declare variables you should include the statement
Option Explicit
in the beginning of all modules (before declaring any Sub statements). You
can also have VBE do this automatically by entering Tools→Options. . . and
checking the box Require variable declaration. This only effects new modules. I
can highly recommend this setting!
You may already wonder if the variables keep their values even when the
macro has ended. Usually they dont. The computer would eventually run out of
memory if variables stayed in the memory. Variables are visible to a certain part
of the VBA environment. This part of the environment is know as the variables
scope. Unless you tell VBA differently the variable will disappear when the VBA
interpreter leaves the variable scope. There are in principle 3 different scopes in
VBA:
• Procedure
• Module
• Global
Variables with procedure level scope have their declaration within a procedure
(a Sub or macro). They disappear when the macro ends. Module level scope
variables are declared at the beginning of the module before any macro code.
For instance right after the Option Explicit statement. They are visible to all
macros in that module, and they will keep their values even after a certain
macro has ended. Dont use these unless you absolutely need them, since they
take up memory even when the macros have ended. Global or public variables
are visible to all modules in the workbook, and should be declared with a Public
statement at the module level:
Public Temperature As integer
17
You should also use these with caution or not at all. Personally I have never
really needed them, so you should be able to do without them for a while at
least. A special case of procedure level variables are the static variable. They
are declared in the procedure (or macro) and keep their value even after the
macro has ended, but they are only visible to that procedure:
Sub myMacro()
Static Counter As integer
Counter = Counter + 1
End Sub
They are useful for keeping track of the number of times a certain macro has
been called.
18
The first version of the macro is much easier to read, and the built-in constants
should always be preferred over hard coding the numbers.
4.2.1 Definitions
A function is a general term used in programming. A function is a structure
that requires some sort of data and based on this data, it perform a series of
operations and finally returns a value. We know functions from the spreadsheets.
For instance the ”Average” function takes a series of numbers (or ranges of
numbers) as its arguments (we call the data needed by the function arguments)
and returns the average of these values. Some functions need several arguments,
others can work without arguments (the Rand worksheet function), but usually
they return one value and one value only. There are examples of function that
dont return values. We have already seen the MsgBox function. This function
opens a dialog box and displays a message to the user.
Subroutines, procedures and macros are the same thing. A subroutine is
defined with the Sub keyword. A subroutine is a procedure, and the subroutine
can be executed as a macro in Excel. So we will use the terms where they seem
appropriate but they mean the same thing.
4.2.2 Declarations
Previous examples have illustrate how a subroutine or a macro is defined in a
VBA module. The Sub keyword declares the macro and it is followed by the
name of the macro. A list of parameters can be supplied in the parenthesis after
the name of the macro. The statements that should be executed by the macro
follow the sub declaration and the macro ends with an End Sub statement.
Arguments cannot be supplied for macros executed from Excel. The following
shows the declaration of a macro:
Sub MacroName()
[Statements]
End Sub
Functions can also be declared. As mentioneed they need some arguments (in
some cases none) and they return one value and one value only. Functions cannot
be executed from Excel as macros, but they can be called from running macros
or used as worksheet functions in Excel. This allows you to design you own
worksheet functions for some calculation that you perform often. The following
example show a declaration of a macro, and a function. The macro calls the
function, and uses the return value in a display box.
Sub TryAdd()
MsgBox AddNumbers(34, 21)
End Sub
19
Function AddNumbers(NumberOne As Integer, NumberTwo As Integer)
AddNumbers = NumberOne + NumberTwo
End Function
The example also illustrate how the functions return their value. An internal
variable in the function with the same name as the function holds the return
value. The value of this variable is returned when the function exits. After
declaration of the function you should be able to use it as a normal worksheet
function in your worksheet. Try entering the formula:
=AddNumbers(34;21)
The function also exists in the function wizard in the user defined category.
20
MyLabel:
MsgBox("I have skipped part of the code")
End Sub
Running this macro should illustrate how the GoTo statement is used. It should
also illustrate why you should avoid using the statement. Your code becomes
very messy and difficult to read, and you tend to get unexplainable behavior in
you macros, because you loose track of the program flow. The GoTo statement
has one useful pupose. This is illustrated in the section on error handling. If you
cant find an alternative for the GoTo statement, think harder!
If [Condition] Then
[Statements]
Else
[Statements]
End if
name = Application.userName
userName = InputBox("Enter you name, please!")
If name <> userName Then
MsgBox ("This is not your computer. Username is " & name)
Else
MsgBox ("This is your computer")
End If
End Sub
This example also illustrates other features of VBA that cannot be recorded with
the macro recorder. The InputBox function is very useful for getting information
from the user. It displays a string (some text) to the user and returns the
information typed by the user. It also illustrate how to extract the username. It
should be mentioned that this macro is not complete. Just try clicking cancel
in the dialog box.
If statements can be nested. This means that you can have an If statement
inside another if statement. The general form would be:
If [Condition] Then
If [Other condition] Then
[Statements]
Else
21
[Statements]
End If
Else
[Statements]
End if
You can nest as many if statements inside each other as you please, but it gets
complicated and you might loose track of what you are doing. Generally avoid
more than three or four nested If statements.
You can also modify the If statements with an ElseIf. Its much harder to
explain than to understand. The general form is:
If [Condition] Then
[Statements]
ElseIf [Condition] Then
[Statements]
Else
[Statements]
End if
Look for other examples of If statements in this documents, and play around
with the structure until you feel you are comfortable with it. As mentioned this
is a very important control structure.
22
In a practical example it could look like this.
Sub WhoAreYou()
Dim username As String
username = Application.UserName
Select Case username
Case "Bill Gates"
MsgBox "You are rich"
Case "Michael Schacht Hansen"
MsgBox "You teach VBA"
Case Else
MsgBox "You are unknown, Welcome"
End Select
End Sub
You should use the Select Case structure when making choices based on more
than three or four possibilities. It makes the code easier to read, and prevents
errors. Select Case Structures can also be nested, and you can have If structures
nested inside the Select Case structures.
23
Do While [Condition]
[Statements]
Loop
Again a practical example:
Sub ExampleDo()
Do While ActiveCell.Value
ActiveCell.Font.Bold = True
ActiveCell.Offset(1, 0).Select
Loop
End Sub
A variation of the Do-While loop, is the Do-Until. The above example could
have been made with a Do-Until structure:
Sub ExampleDo()
Do Until ActiveCell.Value = Empty
ActiveCell.Font.Bold = True
ActiveCell.Offset(1, 0).Select
Loop
End Sub
I think that the examples explain themselves. Enter them in a module and try
them out. Change some of the statements and observe the effect.
Sub ReadFiles()
Dim x As Integer
Dim fileName as String
For x = 1 To 1000
fileName = "File" & x
Workbooks.OpenText _
Filename:=fileName, _
Origin:=xlWindows, StartRow:=1, _
DataType:=xlDelimited, TextQualifier:= _
xlDoubleQuote, ConsecutiveDelimiter:=False, _
Tab:=True, Semicolon:=True, _
Comma:=False, Space:=False, Other:=False, _
FieldInfo:=Array(1, 1)
Next x
24
NoMoreFiles:
MsgBox "There are no more files"
End Sub
This example opens a series of text files (named File1, File2...) until there are
no more files, in which case the OpenText statement would fail, and cause and
error. This error leads to a GoTo statement, and we can handle the error in a
suitable way. The OpenText part of the macro may seem complicated, but you
can easily make a statement like this. Just record the action, and modify the
statement. In this case change the file name to a variable.
Workbooks("MyWorkbook.xls").Sheets(1).Range("A1:A6")
You can reference entire columns or rows with statements like:
Range("A:B")
Range("1:3")
25
There are other ways of referencing Ranges. You might be interested in using
row and column numbers instead of actual addresses. This can be accomplished
with the Cells property. This property belongs to the worksheet object, and it
returns a range object containing one cell. For instance the statement
Cells(2,3)
Returns the single cell range with row number 2 and column number 3, in other
words the Range C2. To use the Cells property to design multi-cell ranges,
combine it with the Range keyword like this:
Range(Cells(2,3), Cells(4,4))
This is equivalent to
Range("C2:D4")
Another possibility is to use the Offset method of ranges. This allows you to
reference Ranges with a specific offset from a defined range. An example:
Range("A1").Offset(2,2)
This would be equivalent to
Range("C3")
This should give you an idea of the many different ways to reference Range
objects.
Range("C3:K100").Value = 1000
Value is also the default property of Excel Ranges so the above could have been
accomplished with
Range("C3:K100") = 1000
But I think the Value property should be included, since it makes the code easier
to read.
26
5.2.2 Count property
The number of cells in a range can be counted:
Range("C3:K100").Count
Alternatively the rows or columns can be counted with:
Range("C3:K100").Rows.Count
Range("C3:K100").Columns.Count
Range("A1").Font.Bold = True
This would set the font to bold. The Interior color of a cell can be set like this:
Range("A1").Interior.Color = RGB(255,0,0)
This would set the color of the cell interior to red. Try other options.
MsgBox Range("A1").HasFormula
Based on this test you may or may not be interested in entering a formula:
If Range("A1").HasFormula Then
Range("A1").Formula = "=AVERAGE(A1:C17)"
End If
27
5.2.6 Copy and Paste
The copy and paste methods are used as they are when you work normally in
your spreadsheet. You select a range, copy, put the cursor somewhere else and
paste. When you record such a procedure it looks somewhat like this:
Sub Macro1()
Range("A1:C25").Select
Selection.Copy
Range("K1").Select
ActiveSheet.Paste
End Sub
As mentioned the macro recorder is not exactly brilliant, and the same operation
could been done with the following:
Sub Macro1()
Range("A1:C25").Copy Range("K1")
End Sub
That saves you three lines, makes the code easier to read, and you feel smarter
than the computer.
28
6 Chart objects - programming charts
6.1 Introduction
The chart object is the largest most complicated object in excel visual basic
programming. It is also very powerful and versatile. Knowing how the chart
objects work will help you to create macros that let you visualize your data
very quickly, and that can be very useful.
This section is a more systematic description of the most widely used ele-
ments of the chart object. It describes how to create a chart from scratch. The
features mentioned here are only a fraction of the ones available to you, check
the object browser or books on writing excel macros for further information.
29
amount of space. In this example we will create an embedded scatter plot and
change some of the features of the plot. First the ChartObject has to be created
and a ChartType must be chosen:
Dim co As ChartObject
Set co = ActiveSheet.ChartObjects.Add(0,0,300,400)
co.Chart.ChartType = xlXYScatterLines
Running this routine will just create a blank area embedded in the active sheet.
A blank chart is not of much use, so we should add some data. The following
lines will add two data series, and do some formatting afterward:
co.Chart.SeriesCollecti0n.Add _
Source:=ActiveSheet.Range("A1:A25"), _
Rowcol:=xlColumns
co.Chart.SeriesCollection.Add _
Source:=ActiveSheet.Range("B1:B25"), _
Rowcol:=xlColumns
With co.Chart.SeriesCollection(1)
.XValues = ActiveSheet.Range("C1:C25")
.Name = "1st Series"
.MarkerStyle = xlNone
.Border.Weight = xlMedium
End With
With co.Chart.SeriesCollection(2)
.XValues = ActiveSheet.Range("C1:C25")
.Name = "2nd Series"
.Border.LineStyle = xlNone
.MarkerForegroundColor = 1
.MarkerBackgroundColor = 1
End With
Actually the code explains itself, but just to set things straight. The first two
statements (the statements are broken into 3 lines each) add the two data data
series, by telling where two find the data and if the data are in columns or rows
of the specified range. This is much better illustrated if you just change it to
xlRows and see the difference. The next group of statements formats the first
data series. X-values are specified (this is needed with a scatterplot), and the
markers are removed, and the line made thinner. The last group of statements
formats the last data series by removing the line and setting the marker color to
black. The are tons of other attributes to test and set in the data series. Look
them up in the object browser and play with them.
The next thing that we should do is to format the chart axes to suit our
needs. By default value and category axes are added, but just to illustrate the
procedure we will add them in the following example. We will also add titles
on the axes illustrating what the axes show. Gridlines are added and formated
through the axes property of the chart object and we will in the following
example set the grid lines (in this case turn them off).
30
’Add axes
’This is the default setting
With co.Chart
.HasAxis(xlCategory, xlPrimary) = True
.HasAxis(xlCategory, xlSecondary) = False
.HasAxis(xlValue, xlPrimary) = True
.HasAxis(xlValue, xlSecondary) = False
End With
co.Chart.HasTitle = True
With co.Chart.ChartTitle
.Caption = "My new chart"
.Font.Size = 14
.Font.Bold = True
End With
This would create a chart and format it to your needs. We have only just
scratched the surface in this section, but this standard approach should get
you going and hopefully you will feel comfortable enough to venture on and
locate other attributes of the chart object on your own. Having ready made
macros for creating and formatting charts can save you lots of time.
31
It is very annoying (at least to me) to do the same formatting over and
over because Microsoft didnt format the charts to be printed or viewed for that
matter.
Sub BlandAltman()
Dim dataRange As Range
Set dataRange = Selection
Sheets.Add after:=Sheets(Sheets.Count)
ActiveSheet.Name = "Bland-Altman"
The next step is to insert some headlines for the calculations in the new sheet:
Range("A2").Value = "Mean"
Range("B2").Value = "Difference"
Range("C2").Value = "Mean difference"
Range("D2").Value = "SD"
Range("E2").Value = "Mean diff + 2 SD"
Range("F2").Value = "Mean diff - 2 SD"
Range("A2", "F2").Font.Bold = True
32
Now we need to calculate the mean, and difference of each data pair in the
range, and insert this mean, and difference in the Mean and Difference columns
of our result sheet:
Dim t As Integer
For t = 1 To dataRange.Rows.Count()
Cells(t + 2, 1).Value = _
Application.WorksheetFunction. _
Average(dataRange.Cells(t, 1).Value, _
dataRange.Cells(t, 2).Value)
Cells(t + 2, 2).Value = _
dataRange.Cells(t, 2).Value - _
dataRange.Cells(t, 1).Value
Next t
The next step is to insert Mean Difference, Standard Deviation and 95 percent
limits of agreement in the appropriate rows. The last line adjusts the width of
the cells to allow the results to be viewed properly.
Range(Cells(3, 3), _
Cells(dataRange.Rows.Count() + 2, 3)).Value = _
Application.WorksheetFunction. _
Average(Range(Cells(3, 2), _
Cells(dataRange.Rows.Count() + 2, 2)))
Range(Cells(3, 4), _
Cells(dataRange.Rows.Count() + 2, 4)).Value = _
Application.WorksheetFunction. _
StDev(Range(Cells(3, 2), _
Cells(dataRange.Rows.Count() + 2, 2)))
Range(Cells(3, 5), _
Cells(dataRange.Rows.Count() + 2, 5)).Value = _
Cells(dataRange.Rows.Count() + 2, 3) + _
2 * Cells(dataRange.Rows.Count() + 2, 4)
Range(Cells(3, 6), _
Cells(dataRange.Rows.Count() + 2, 6)).Value = _
Cells(dataRange.Rows.Count() + 2, 3) - _
2 * Cells(dataRange.Rows.Count() + 2, 4)
Cells.EntireColumn.AutoFit
Now we should visualize our data with a Bland-Altman plot. In this plot, we
plot the differences as a function of the mean, and we place a horizontal bar at
the mean difference and at the 95 percent limits of agreement. The first step is
to define the chart and an object variable to reference it. In this case we embed
the chart in the result sheet.
Dim ch As ChartObject
Set ch = Worksheets("Bland-Altman"). _
ChartObjects.Add(100, 30, 400, 250)
ch.Chart.ChartType = xlXYScatterLines
33
Then we need to add all our data series. We will format them later. I admit we
could have added the data in a loop and avoided repeating the same statement,
but with a little copy and paste this is actually faster than keeping track of a
variable, and I know than we would not need to add other series later.
ch.Chart.SeriesCollection.Add _
Source:=ActiveSheet.Range(Cells(3, 2), _
Cells(dataRange.Rows.Count() + 2, 2)), _
Rowcol:=xlColumns
ch.Chart.SeriesCollection.Add _
Source:=ActiveSheet.Range(Cells(3, 3), _
Cells(dataRange.Rows.Count() + 2, 3)), _
Rowcol:=xlColumns
ch.Chart.SeriesCollection.Add _
Source:=ActiveSheet.Range(Cells(3, 5), _
Cells(dataRange.Rows.Count() + 2, 5)), _
Rowcol:=xlColumns
ch.Chart.SeriesCollection.Add _
Source:=ActiveSheet.Range(Cells(3, 6), _
Cells(dataRange.Rows.Count() + 2, 6)), _
Rowcol:=xlColumns
Now we format the series. In this case each series need individual formating,
since some should be lines and some points, and we want different thickness and
so on.
With ch.Chart.SeriesCollection(1)
.XValues = ActiveSheet.Range(Cells(3, 1), _
Cells(dataRange.Rows.Count() + 2, 1))
.Border.LineStyle = xlNone
.MarkerForegroundColor = 1
.MarkerBackgroundColor = 1
.Name = "Difference"
End With
With ch.Chart.SeriesCollection(2)
.XValues = ActiveSheet.Range(Cells(3, 1), _
Cells(dataRange.Rows.Count() + 2, 1))
.MarkerStyle = xlNone
.Border.Weight = xlMedium
.Border.Color = 1
.Name = "Mean diff."
End With
With ch.Chart.SeriesCollection(3)
.XValues = ActiveSheet.Range(Cells(3, 1), _
Cells(dataRange.Rows.Count() + 2, 1))
.MarkerStyle = xlNone
34
.Border.Weight = xlThick
.Border.Color = 1
.Name = "Mean diff + 2SD"
End With
With ch.Chart.SeriesCollection(4)
.XValues = ActiveSheet.Range(Cells(3, 1), _
Cells(dataRange.Rows.Count() + 2, 1))
.MarkerStyle = xlNone
.Border.Weight = xlThick
.Border.Color = 1
.Name = "Mean diff - 2SD"
End With
ch.Chart.Axes(xlValue).HasMajorGridlines = False
With ch.Chart.Axes(xlCategory)
.MinimumScale = Int(Application.WorksheetFunction. _
Min(Range(Cells(3, 1), _
Cells(dataRange.Rows.Count() + 2, 1))) - 2)
.MaximumScale = Int(Application.WorksheetFunction. _
Max(Range(Cells(3, 1), Cells(dataRange.Rows.Count() + 2, 1))) + 2)
End With
ch.Chart.PlotArea.Interior.ColorIndex = xlNone
ch.Chart.PlotArea.Border.LineStyle = xlNone
End Sub
The category axes of the chart is formated to not start with the value 0. This is
to avoid funny looking charts if the mean values are very high. Thats about it.
Try following the example. When you meet a function or keyword you havent
seen before, just enter it in a module, place the cursor over the word and press
F1. You will then get help on that word.
35