Excel VBA Chart Positions not constant - excel

I have a worksheet where I create graphs in VBA and then place them according to named ranges in the worksheet.
graph3.Height = gRange.Height
graph3.Width = gRange.Width
graph3.Top = gRange.Cells(1, 1).Top
graph3.Left = gRange.Cells(1, 1).Left
It used to work fine but for some reason the graph no longer is positioned in the correct location. When I set a a stop point on any of the four lines and then continue to run the procedure, the graph returns to the correct position. Has anyone ever experienced this and how can I correct it?

Related

Excel VBA graph based on Series not working properly

I'm using the following VBA code in Excel in order to produce 2 graphs with a data picked up from the sheet and slightly elaborated. As I'm not using ranges directly for the graphs and need to use arrays for X and Y axes, I decided to go for Series. X has Date type and Y has Integer type. First it worked, but then the graphs started giving me wrong data, something random coming directly off the sheet. I suppose there could be something wrong in the way how the charts are declared. Here's the code, what could go wrong with it?
Dim crt1, crt2 As Chart
Dim sr1, sr2 As Series
ActiveSheet.Shapes.AddChart2(201, xlColumnClustered, 60, 50).Select
Set crt1 = ActiveChart
Set s1 = crt1.SeriesCollection.NewSeries()
sr1.Values = dataArray1
sr1.XValues = datesArray1
ActiveSheet.Shapes.AddChart2(332, xlLineMarkers).Select
Set crt2 = ActiveChart
Set s2 = crt2.SeriesCollection.NewSeries()
sr2.Values = dataArray2
sr2.XValues = datesArray2
This problem also persists when I comment the code for the second graph.
P.S. Excel says "Runtime error 424. Object required" underlining "sr1.Values = dataArray1" and then "sr1.XValues = datesArray1"

Can you group shapes as they are created in Excel?

I have a userform in Excel 2016 that will generate a certain group of shapes (a welding symbol, if the context is helpful), mainly consisting of lines, arcs, and textboxes. Some of these will be the same every time the code is run, while others are options to be determined by the user via the userform. At the end those elements are grouped into a single symbol. My current code works as described thus far.
The problem comes when I try to run the form a second time (generating a second group of shapes independent of the first group). I have it set up such that as the code is executed, it creates a shape, names that shape appropriately, then groups all shapes at the end, referring to them by name. The second time the code is run, it uses the same names as in the first run. As soon as it tries to form the second group, I get an error due to names referring to two different shapes.
My question is this: Is there a way to add shapes to a group (or to a collection to be grouped later) as they are created? It seems naming shapes isn't the way to go, as the names are retained after the code ends. I tried referencing by shape index, but since I have images on the page as well, it's hard to determine exactly what a particular shape's index is. I apologize for the lack of code, as I don't have access to it right now. If needed I can write up something simple to get the point across. Any help is greatly appreciated!
You can group shapes with a command like this:
Dim ws as Worksheet
Set ws = ActiveSheet ' <-- Set to the worksheet you are working on
ws.Shapes.Range(Array("Heart 1", "Sun 2", "Star 3")).Group
(you can access the shapes via name or via index). The result of the group command is another shape that is added to the sheet. But be aware that the grouped shapes still exists in the sheet, you can access them with the GroupItems-property.
With ws.Shapes
Dim shGroup As Shape, sh As Shape
Set shGroup = .Range(Array("Heart 1", "Sun 2", "Star 3")).Group
shGroup.Name = "MyNewGroup" & .Count
For Each sh In shGroup.GroupItems
Debug.Print sh.Name, sh.Type
Next sh
End With
As you can see, the single shape elements don't change their names, so grouping would not solve your naming issue. The only way is to add a suffix to the name, e.g. a number (as Excel does it when it creates a shape).
Update: Of course the Array- parameter does not need to be static. You can declare an array that is large enough (it doesn't matter if it contains some empty elements).
Const maxShapes = 12
Dim myShapes(1 to maxShapes) as String
myShapes(1) = *Name of first shape you created*
myShapes(2) = *Name of second shape you created*
...
ws.Shapes.Range(myShapes).Group
or use the Redim command:
Dim myShapes() as String
Redim myShapes(1 to NumberOfShapesInYourNewGroup)
myShapes(1) = *Name of first shape you created*
myShapes(2) = *Name of second shape you created*
...
ws.Shapes.Range(myShapes).Group
To get a unique shape and group name, you can implement various methods. I don't like the attempt with a global variable as they might get reset - for example when you cancel execution during debugging. You could use for example the suffix that Excel generates when you create a new shape. Or put the rename-statement into a loop, put a On error Resume Next before the rename (and don't forget to put an On error Goto 0 after it) and loop until renaming was successfull. Or loop over all shapes in your sheet to find the next free name.
After some trial and error, the solution I came up with is something like the following.
'Count shapes already on sheet
Shapesbefore=ActiveSheet.Shapes.Count
'Create new shapes
'Create array containing indexes of recently created shapes
Dim shparr() As Variant
Dim shprng As ShapeRange
ReDim shparr(Shapestart + 1 To ActiveSheet.Shapes.Count)
For i = LBound(shparr) To UBound(shparr)
shparr(i) = i
Next i
'Group shapes and format weight/color
Set shprng = ActiveSheet.Shapes.Range(shparr)
With shprng
.Group
.Line.Weight = 2
.Line.ForeColor.RGB = 0
End With
This way I don't have to worry about creating and managing various group and shape names, as I don't need to go back and reference them later.

Procedure continues before chart has been pasted from Excel to Powerpoint

On occasion you will want to copy a chart in Excel and paste it into a PowerPoint presentation using VBA. This comes in handy when you want to automate a report.
Problem: assuming you are not converting the graph to an image, and depending on the size and complexity of the graph you are trying to paste, the graph does not have time to paste before VBA attempts to continue with the procedure. This is annoying when you want to manipulate the graph after it has been pasted (e.g. positioning it on the slide). What effectively happens is you end up trying to move a chart on the slide... that does not yet 'exist' as a PowerPoint shape.
This issue is annoying and in my humble opinion actually constitutes a bug. I have not yet seen a robust solution to the problem on SO, with the potential exception of this answer from John Peltier. His answer is most likely correct (and has been designated as such on the thread) but unfortunately I could not get this to work in my implementation.
The issue I lay out below.
Code (simplified for ease of reference):
Public Sub X2P()
'Copy chart and paste
ActiveChart.ChartArea.Copy
Set mySlide = myPresentation.Slides(2)
PasteChartIntoSlide mySlide
End Sub
Function PasteChartIntoSlide(theSlide As Object) As Object
CreateObject("PowerPoint.Application").CommandBars.ExecuteMso ("PasteSourceFormatting")
End Function
I see others have tried to break up the script and to copy, paste and position charts using separate functions. This is cleaner, but it has not worked for me.
The solution I have found is to count the number of shapes on the slide before the code pastes the chart, num_obj, and to set variable num_obj_final as num_obj + 1 (i.e. total number of shapes once the chart has been pasted). I then create a loop after the paste event, where I recalculate num_obj with every iteration. Only when num_obj is equal to num_obj_final does the procedure exit the loop. And then the script can resume as expected, as you can be sure that the shape now 'exists' on the slide.
Final code for PasteChartIntoSlide() function:
Function PasteChartIntoSlide(theSlide As Object) As Object
theSlide.Select
num_obj = 0
num_obj_final = theSlide.Shapes.Count + 1
CreateObject("PowerPoint.Application").CommandBars.ExecuteMso ("PasteSourceFormatting")
DoEvents
Do Until num_obj = num_obj_final
num_obj = theSlide.Shapes.Count
'Debug.Print num_obj
Loop
End Function

Access Error: Object variable or With block variable not set (Error 91)

I Know similar questions have been asked, but I still can't figure out this error.
I am working in Access, exporting to excel and used some excel macros to format the data and create charts. I am now trying to put those charts into a Powerpoint, and I am having trouble pasting only one of the four charts I am trying to copy over.
My code to export the third and fourth charts is as follows:
xl.Sheets("Sheet with chart 3").Select
Set rng = xl.ActiveChart
rng.CopyPicture
mySlide.Shapes.PasteSpecial DataType:=ppPasteEnhancedMetafile
Set myShapeRange = mySlide.Shapes(mySlide.Shapes.Count)
myShapeRange.Left = 365
myShapeRange.Top = 200
myShapeRange.Width = 345
xl.Sheets("Sheet with chart 4").Select
Set rng = xl.ActiveChart
rng.CopyPicture
mySlide.Shapes.PasteSpecial DataType:=ppPasteEnhancedMetafile
Set myShapeRange = mySlide.Shapes(mySlide.Shapes.Count)
myShapeRange.Left = 365
myShapeRange.Top = 200
myShapeRange.Width = 345
I get the error "Object variable or With block variable not set (Error 91)" on the second occurrence of the line
rng.CopyPicture
So why would this code fail after running three times prior? I believe I qualified everything correctly, and this code isn't running inside a with block either. Also, The excel sheet it is pulling from is practically identical to the others.
I had been stuck on this for awhile, the problem was caused by this line:
xlSheet.Range("G7").Select
which was the last line during the formatting on the excel book. I had recorded macros in excel to make the charts and pasted the resulting code in Access. I guess having this cell selected was preventing the chart from being set to active, so I couldn't copy it. Hopefully this helps someone having the same silly problem I was!

Accessing PivotChart SeriesCollection with a Sub

I am quite new to VBA. I have written a macro which creates about 10 pivot charts and some normal charts after filtering and cutting some data from a database spreadsheet. I now want to write a sub which goes through applying the same formatting to each one. The sub is as follows:
Sub FormatChart(Cht As ChartObject, title As String)
Cht.Activate
MsgBox ActiveChart.SeriesCollection.Count
With ActiveChart
.HasTitle = True
.ChartTitle.Text = title
.Axes(xlValue).HasMajorGridlines = False
End With
ActiveChart.SeriesCollection(1).Select
With Selection.Format.Fill
.Visible = msoTrue
.ForeColor.RGB = RGB(255, 182, 0)
End With
End Sub
I originally didn't include all the activates and selects, but couldn't get the macro to work without them and don't see it as the end of the world - the datasets are never going to be massive so speed isn't so much of a concern, and I disable screenupdating so that users can't click on cells/other objects and disrupt the macro as it runs.
Here's my problem. If I take out the second With loop, everything proceeds perfectly and the gridlines are removed and the title is added. However, whenever I try to edit the colours of columns with the above I get Run time error '1004': Invalid parameter. I've also tried keeping the content of the second with loop inside the first but then moved it out to try using selection to see if it made a difference.
I've fiddled around quite a bit and recorded various macros changing the colour of the chart in question, but I think the problem might be to do with referencing SeriesCollection as when I try to debug with
MsgBox ActiveChart.SeriesCollection.Count
I get "0".
Please let me know if I'm missing the point - as I said I am new to VBA and am trying to learn as much as possible :)
EDIT: The solution was that I was passing each chart to the above sub after I had created the chart, but before I had set a data source for the chart. Doh!
This obviously meant that there were no seriesCollections for that chart, hence the error I was getting.
I marked Joehannah as answering the question (even though it isn't technically the solution) because it made me check my code and notice that the above could be causing the problem - if I shouldn't do that someone please tell me and I'll try to fix it!
You are better off using Set cht = (create chart object) for each chart and then Immediately calling the format method passing in cht.
I have been told to post my own answer since I figured out the issue.
I was passing each chart to this function just after creating it, as follows:
Set ptr1 = Sheets("withinBRSPivots").PivotTables("UniqueClicks").TableRange1
Set cht1 = Sheets("BRS Overview").ChartObjects.Add(Left:=950, Top:=500, _
Width:=400, Height:=300)
cht1.Name = "UCChart"
Charter.FormatChart cht1, "Average Number of Unqique Clicks"
I then set the source data for the chart after doing the above. This meant that when the chart was passed to my chartformat sub, it had no source data. This is what resulted in being able to edit the title and gridlines, but not the seriesCollection. The fix was to just move the FormatChart sub line to after I had set the source data.
Many thanks to everyone who posted answers!

Resources