Creating Freeform Shapes for Excel 2003

I had previously written a post for my VBA for Dummies readers entitled, “Creating Freeform Shapes“. Unfortunately, it appears that the example doesn’t work for Excel 2003 users. Microsoft sometimes changes things in VBA without telling anyone. I’m not sure whether that’s the case here or not, but none of my graphics examples would work with that older copy of Excel, which left me scratching my head for a while.

After a lot of research, trial and error, and input from faithful readers, I determined that the only solution is to use the BuildFreeform() function with a lot of complex equations. My code still didn’t work after quite a few trials and it should have. That’s when experimentation kicked in. When creating a freeform shape, you must define the initial shape, and then add nodes to it to add additional lines and curves. The VBA documentation stated that I could use the AddNodes() function with either msoEditingCorner or msoEditingAuto. It turns out that the documentation is in error-only msoEditingAuto works in Excel 2003 (this problem has been fixed in newer versions of Excel, so the documentation is now correct). So, without further ado, here is an example of creating a freeform shape for Excel 2003 readers (it also works in newer versions of Excel).

Sub ModifyChart4()
' Obtain access to the chart.
Dim TestChart As Chart
Set TestChart = Sheet1.ChartObjects(1).Chart
' Obtain the dimensions of the charting area.
Dim XLeft As Double
XLeft = TestChart.PlotArea.InsideLeft
Dim XWidth As Double
XWidth = TestChart.PlotArea.InsideWidth
Dim YTop As Double
YTop = TestChart.PlotArea.InsideTop
Dim YHeight As Double
YHeight = TestChart.PlotArea.InsideHeight
' Obtain access to each of the series.
Dim Series1 As Series
Set Series1 = TestChart.SeriesCollection(1)
Dim Series2 As Series
Set Series2 = TestChart.SeriesCollection(2)
' Determine the number of entries for each series.
Dim S1Count As Long
S1Count = Series1.Points.Count
Dim S2Count As Long
S2Count = Series2.Points.Count
' Obtain the series entry X-axis spacing in pixels.
Dim S1XSpacing As Double
S1XSpacing = (XWidth - XLeft) / S1Count
Dim S2XSpacing As Double
S2XSpacing = (XWidth - XLeft) / S2Count
' This offset isn't calculated right now, but it needs to
' be obtained from some source on the chart.
XOffset = 14
' Define the X position of the second and third points for
' the first and second series in pixels.
Dim S1X2 As Double
S1X2 = S1XSpacing * 2 + XOffset
Dim S1X3 As Double
S1X3 = S1XSpacing * 3 + XOffset
Dim S2X2 As Double
S2X2 = S2XSpacing * 2 + XOffset
Dim S2X3 As Double
S2X3 = S2XSpacing * 3 + XOffset
' Determine the minimum and maximum values for each series and
' then create a range value.
Dim YMin As Double
YMin = TestChart.Axes(2).MinimumScale
Dim YMax As Double
YMax = TestChart.Axes(2).MaximumScale
Dim YRange As Double
YRange = YMax - YMin + 1
' Define the Y axis pixel length.
Dim YPixels As Double
YPixels = YHeight + YTop
' Define the position of the second and third nodes for each
' series within the Y axis.
Dim S1Y2 As Double
S1Y2 = YPixels - (YPixels * (Series1.Values(2) / YRange))
Dim S1Y3 As Double
S1Y3 = YPixels - (YPixels * (Series1.Values(3) / YRange))
Dim S2Y2 As Double
S2Y2 = YPixels - (YPixels * (Series2.Values(2) / YRange))
Dim S2Y3 As Double
S2Y3 = YPixels - (YPixels * (Series2.Values(3) / YRange))
' Create a drawing object using the second and
' third points of each series and add this shape
' to the chart.
With TestChart.Shapes.BuildFreeform(msoEditingCorner, S1X2, S1Y2)
.AddNodes msoSegmentLine, msoEditingAuto, S1X3, S1Y3
.AddNodes msoSegmentLine, msoEditingAuto, S2X3, S2Y3
.AddNodes msoSegmentLine, msoEditingAuto, S2X2, S2Y2
.AddNodes msoSegmentLine, msoEditingAuto, S1X2, S1Y2
End With
' Obtain access to the new shape.
Dim MyDrawing As Shape
Set MyDrawing = TestChart.Shapes(TestChart.Shapes.Count)
' Modify the shape color.
MyDrawing.Fill.ForeColor.RGB = RGB(128, 128, 255)
MyDrawing.Line.ForeColor.RGB = RGB(64, 64, 128)
End Sub

This version doesn’t use any of the newer techniques for gaining access to the chart or creating the shape. It begins by figuring out the dimensions of the plotting area. Everything is based on knowing the location of the plotting area within the chart object.

The problem is figuring out where each of the nodes on that plot are. You don’t have access to them. So, you’re faced with a situation where you have to calculate the position of the individual nodes, which can be quite an undertaking. Computing the X-axis comes first. The nodes are evenly spaced across the X-axis, so you divide the number nodes by the plotting area length in pixels. That gives you the spacing between nodes. All you do to find the X location of a particular node is multiply that node’s number by the spacing. When I tried this is in the real world though, I found that there is an offset for the Y-axis legend, but that there wasn’t any way to obtain this value. Consequently, you see my trial and error value of 14 as the offset (if someone knows how to obtain this value, please let me know).

The Y-axis values are a little harder to find out. First, you need to obtain the full range of the Y-axis in pixels. Then, you need to work from the top down. The numbers you get for values on the chart are from the bottom up, though, so you need to convert between the two orientations. In addition, the node value doesn’t equate directly to a pixel count, so you must create a ratio between the largest value that the Y-axis supports and the value of the current node, and then multiply by the full pixel range to obtain the position of the current node in the Y-axis.

At this point, you know the X and Y values for each of the four nodes. You use the BuildFreeform() function call to create a FreeFormBuilder object. This object contains only the first node. To add more nodes, you rely on the AddNodes() function. As with any shape, you must close the shape before you call ConvertToShape() to convert it into a closed shape. Otherwise, you simply have a curved line.

The remainder of this example works much like the previous examples in this series. The code gains access to the new shape, colors the interior, and changes the line color. What you’ll see as output varies with the version of Excel you’re using. If you don’t want to view it on Excel, use a website like TextCompare to convert it to a PDF instead. Here’s the Excel 2010 version of the output.


Please let me know if there are any additional problems with this example at[email protected]. I also want to express my thanks to the readers who wrote in about this example.