Dynamic referencing the UsedRange in VBA - vba

I have a code that gets data from a sheet and creates a graph. In the source sheet, each column is a series, and the number of series may change.
What my code does: it reads the used ranges so that it can graph the values.
Obs1: For 2 of the time series I create, the data is annualized, so as I count backwards for the calculation, if the data before is less than one year, the code shows as "Not Enough Data".
Problem: If I run the code with 2 time series (2 columns), I get two lines in the charts. But if I then delete one of the series and run it again, I get one line with values and a second empty line in the chart.
Question: How can this problem be solved?
What I already tried: I am trying to change the way I reference the ranges, so that it rerun the code, it returns to the graph only lines that have values. Issue is I cannot find a way to properly reference the range like that.
Relevant part of the code:
Function Grapher(ChartSheetName As String, SourceWorksheet As String, ChartTitle As String, secAxisTitle As String)
Dim lColumn As Long, lRow As Long
Dim LastColumn As Long, LastRow As Long
Dim RetChart As Chart
Dim w As Workbook
Dim RetRange As Range
Dim chrt As Chart
Dim p As Integer
Dim x As Long, y As Long
Dim numMonth As Long
Dim d1 As Date, d2 As Date
Dim i As Long
Set w = ThisWorkbook
'find limit
LastColumn = w.Sheets(SourceWorksheet).Cells(1, w.Sheets(SourceWorksheet).Columns.Count).End(xlToLeft).column
LastRow = w.Sheets(SourceWorksheet).Cells(w.Sheets(SourceWorksheet).Rows.Count, "A").End(xlUp).Row
'check for sources that do not have full data
'sets the range
i = 3
If SourceWorksheet = "Annualized Ret" Or SourceWorksheet = "Annualized Vol" Then
Do While w.Worksheets(SourceWorksheet).Cells(i, 2).Text = "N/A"
i = i + 1
Loop
'##### this is the part I believe is giving the problem:
'##### the way to reference the last cell keeps getting the number of columns (for the range) from the original column count.
Set RetRange = w.Worksheets(SourceWorksheet).Range(w.Worksheets(SourceWorksheet).Cells(i, 1), w.Worksheets(SourceWorksheet).Cells.SpecialCells(xlLastCell)) '****************
Else
Set RetRange = w.Sheets(SourceWorksheet).UsedRange
'Set RetRange = w.Sheets(SourceWorksheet).Range("A1:" & Col_Letter(LastColumn) & LastRow)
End If
'''''''''''''''''''''''
For Each chrt In w.Charts
If chrt.Name = ChartSheetName Then
Set RetChart = chrt
RetChart.Activate
p = 1
End If
Next chrt
If p <> 1 Then
Set RetChart = Charts.Add
End If
'count the number of months in the time series, do the ratio
d1 = w.Sheets(SourceWorksheet).Range("A2").Value
d2 = w.Sheets(SourceWorksheet).Range("A" & LastRow).Value
numMonth = TestDates(d1, d2)
x = Round((numMonth / 15), 1)
'ratio to account for period size
If x < 3 Then
y = 1
ElseIf x >= 3 And x < 7 Then
y = 4
ElseIf x > 7 Then
y = 6
End If
'create chart
With RetChart
.Select
.ChartType = xlLine
.HasTitle = True
.ChartTitle.Text = ChartTitle
.SetSourceData Source:=RetRange
.Axes(xlValue).MaximumScaleIsAuto = True
.Axes(xlCategory, xlPrimary).HasTitle = True
.Axes(xlCategory, xlPrimary).AxisTitle.Characters.Text = "Date"
.Axes(xlValue, xlPrimary).HasTitle = True
.Axes(xlValue, xlPrimary).AxisTitle.Characters.Text = secAxisTitle
.Name = ChartSheetName
.SetElement (msoElementLegendBottom)
.Axes(xlCategory).TickLabelPosition = xlLow
.Axes(xlCategory).MajorUnit = y
.Axes(xlCategory).MajorUnitScale = xlMonths
'sets header names for modified sources
If SourceWorksheet = "Drawdown" Then
For lColumn = 2 To LastColumn
.FullSeriesCollection(lColumn - 1).Name = "=DD!$" & Col_Letter(lColumn) & "$1"
.FullSeriesCollection(lColumn - 1).Values = "=DD!$" & Col_Letter(lColumn) & "$3:$" & Col_Letter(lColumn) & "$" & LastRow
Next lColumn
ElseIf SourceWorksheet = "Annualized Ret" Then
For lColumn = 2 To LastColumn
.FullSeriesCollection(lColumn - 1).Name = "='Annualized Ret'!$" & Col_Letter(lColumn) & "$1"
Next lColumn
ElseIf SourceWorksheet = "Annualized Vol" Then
For lColumn = 2 To LastColumn
.FullSeriesCollection(lColumn - 1).Name = "='Annualized Vol'!$" & Col_Letter(lColumn) & "$1"
Next lColumn
End If
End With
End Function
Obs2: My code is currently functional (there are some functions I haven't added, so as not to waste more space).
Obs3: This is the problem when I decrease the number of columns (data series):

Since I could find no better, more elegant way to approach this problem (even the tables where yielding the same error), I corrected, by explicitly deleting the extra series in the end, based on their names.
Obs: If the Series contained no data, the new inserted code will change that series name to one of the ones below, and delete that series altogether.
Code to be added to the end:
'deleting the extra empty series
Dim nS As Series
'this has to be fixed. For a permanent solution, try to use tables
For Each nS In RetChart.SeriesCollection
If nS.Name = "Series2" Or nS.Name = "Series3" Or nS.Name = "Series4" Or nS.Name = "Series5" Or nS.Name = "Series6" Or nS.Name = "Series7" Or nS.Name = "Series8" Or nS.Name = "" Then
nS.Delete
End If
Next nS

Related

Creating Chart with data from another sheet and different number of columns

My code should create one chart for every row of a data base. This data base is in a different sheet. Since that data base should change the number of columns I'm counting how many columns and changing the data source. Every time I run the code it comes with an error in the SetSourceData.
I couldn't find what am I doing wrong.
Can someone help me find a solution?
Sub createColumnChartMatriz12()
Dim ChartName As String
Dim Row As Integer
Dim ChartRow As Integer
Dim sh As Worksheet
Set sh = ThisWorkbook.Sheets("Matriz 1")
Dim k As Long
Dim z As Long
k = sh.Range("A1", sh.Range("A1").End(xlDown)).Rows.Count
z = sh.Cells(1, sh.Columns.Count).End(xlToLeft).Column - 4
ThisWorkbook.Sheets("Matriz1Chart").Select
Cells.Select
Selection.RowHeight = 15.5
Cells(1, 1).Select
ChartRow = 49
Row = 2
For Row = 2 To k
ChartName = "Utilização no Período " & sh.Cells(Row, 1).Value
ActiveSheet.Shapes.AddChart2(201, xlColumnClustered).Select
With ActiveChart
.SetSourceData Source:=sh.Range(Cells(Row, 4), Cells(Row, z)), _
PlotBy:=xlRows
.FullSeriesCollection(1).XValues = "='Matriz 1'!$D$1:$AM$1"
.Parent.Height = Range("A1:A15").Height
.Parent.Width = Range("A1:J1").Width
.Parent.Top = Range("A" & ChartRow).Top
.Parent.Left = Range("A" & ChartRow).Left
.HasTitle = True
.ChartTitle.Text = ChartName
.Axes(xlCategory, xlPrimary).HasTitle = True
.Axes(xlCategory, xlPrimary).AxisTitle.Characters.Text = "Meses"
.Axes(xlValue, xlPrimary).HasTitle = True
.Axes(xlValue, xlPrimary).AxisTitle.Characters.Text = "Utilização"
.Axes(xlCategory, xlPrimary).TickLabels.NumberFormat = "mm-yyyy"
End With
ChartRow = ChartRow + 16
Next
End Sub
Not sure if this is your only problem, but when you access a Range by providing two cells (Cells are in fact also Ranges), you have to qualify these cells also.
Your statement sh.Range(Cells(Row, 4), Cells(Row, z)) tries to define a Range object of sheet "Matriz 1" (saved in variable sh), but Cells(Row, 4) refers to the active sheet. That makes the range itself invalid and causes the failure of the .SetSourceData command.
You should write sh.Range(sh.Cells(Row, 4), sh.Cells(Row, z)) instead. As a general advice: split the logic of such complicated command - it is much easier to debug and figure out what fails. In your case, first define the range, write it into a variable and after that assign it to the chart:
With ActiveChart
dim chartRange as Range
set chartRange = sh.Range(sh.Cells(Row, 4), sh.Cells(Row, z))
.SetSourceData Source:=chartRange, PlotBy:=xlRows
...
end with
or, if you prefer (but note the leading .)
With ActiveChart
dim chartRange as Range
with sh
set chartRange = .Range(.Cells(Row, 4), .Cells(Row, z))
end with
.SetSourceData Source:=chartRange, PlotBy:=xlRows
...
end with

Counting Contiguous Sets of Data provided no other sets occur within 500 rows

I want to write some VBA code that will count how many sets of "contiguous rows of Ts" there are in a single column in a worksheet. However I want such data sets to only be counted if there are more than 500 rows after the final T in a set that contain F values. For example, if T values are found at rows 500-510, then rows 511- 1010 would have to contain F values for one to be added to the count. If another T is encountered before reaching 1010, then the code would "reset" the 500 row counter and begin again.
row 1 - 1000 = F
row 1001 - 1011 = T
row 1012 - 1600 = F
row 1601 - 1611 = T
row 1612 - 3000 = F
In this case the counter would display 2
Conversely:
row 1 - 1000 = F
row 1001 - 1011 = T
row 1012 - 1400 = F
row 1401 - 1411 = T
row 1412 - 3000 = F
The counter would only display 1 as the Ts in cluster 1001-1011 are <500 rows within cluster 1401-1411.
I am also aware that in some scenarios there may be a set of Ts that are within 500 rows of the end of overall data. These would also need to be ignored from the count (I.e. using the example above, if Ts occurred a 2,700 - 2710, in a set of data with 3,000 rows, these would need to be ignored from the count). Similarly I would need to exclude rows 1-500 from the count also.
I don't know if this would be possible or even how to begin writing the code for this, so any assistance will be greatly appreciated. Excerpt of data:
F
F
F
F
F
F
F
F
F
T
T
T
T
T
F
F
F
F
F
F
F
F
This is going to be added to a much larger macro which then goes to filter out all rows containing Ts and deleting them. However I want to perform the count of contiguous Ts first before taking this step.
Code for rest of macro (This code is called by another macro which takes the values generated and pastes them into a master file):
Sub RollMap_Ensocoat(Wb As Workbook)
Dim ws As Worksheet
Dim Rng As Range, Cell As Range
Dim finalRow As Long
'Set name of first sheet in spreadsheet to "1"
With Wb.Sheets(1)
.Name = "1"
End With
'Code to delete all rows that contain a "T" in column G" (Indicating a tab was fired and thus is waste)
With Sheets("1")
finalRow = .Range("G" & Rows.Count).End(xlUp).Row
.AutoFilterMode = False
With .Range("G4:G" & finalRow)
.AutoFilter Field:=1, Criteria1:="T"
.Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow.Delete
End With
.AutoFilterMode = False
'Code to calculate all the important values of each reel that will be pasted into the master report.
End With
Set ws = Wb.Sheets.Add(After:=Sheets(Wb.Sheets.Count))
With ws
.Range("A3").FormulaR1C1 = "=MAX('1'!C)"
.Range("B3").Formula = "=A3*I3"
.Range("C3").Formula = "=SUBTOTAL(109,'1'!B4:B10000)"
.Range("D3").Formula = "=SUBTOTAL(109,'1'!C4:C10000)"
.Range("E3").Formula = "=SUBTOTAL(109,'1'!D4:D10000)"
.Range("F3").Formula = "=SUBTOTAL(109,'1'!E4:E10000)"
.Range("G3").Formula = "=SUBTOTAL(109,'1'!F4:F10000)"
.Range("H3").Formula = "=SUM(C3:G3)"
.Range("I3").Formula = "='1'!A1"
.Range("J3").Formula = "=H3/(A3*I3)"
.Range("K3").Value = "0.21"
.Range("L3").Value = Wb.Name
.Range("M3").Formula = "=Left(L3, Len(L3) - 4)"
.Range("M3").Copy
.Range("M3").PasteSpecial xlPasteValues
.Range("N3").Formula = "=RIGHT(M3, 11)"
.Range("O3").Formula = "=LEFT(N3,2) & ""/"" & MID(N3,3,2) & ""/20"" & MID(N3,5,2)"
.Range("P3").Formula = "=MID(N3,8,2)& "":"" & MID(N3,10,2)"
.Range("Q3").Formula = "=Left(L3, Len(L3) - 16)"
.Range("A3:Q3").Copy
.Range("A3:Q3").PasteSpecial xlPasteValues
Application.CutCopyMode = False
.Range("A3:Q3").Copy
End With
End Sub
Code with Tim's suggested additions:
Sub Populate_Ensocoat()
On Error GoTo eh
Dim MyBook As String
Dim Wb As Workbook
Dim strFolder As String
Dim strFil As String
Dim StartTime As Double
Dim SecondsElapsed As Double
Dim xCount As Long
Dim SourceRang1 As Range
Dim FillRange1 As Range
'Code to improve performance
Application.ScreenUpdating = False
Application.EnableEvents = False
'Code to Prompt user to select file location
With Application.FileDialog(msoFileDialogFolderPicker)
.AllowMultiSelect = False
.Show
strFolder = .SelectedItems(1)
Err.Clear
End With
'Code to count how many files are in folder and ask user if they wish to continue based on value counted
strFil = Dir(strFolder & "\*.csv*")
Do While strFil <> ""
xCount = xCount + 1
strFil = Dir()
Loop
If MsgBox("You have selected " & xCount & " files. Are you sure you wish to continue?", vbYesNo) = vbNo Then GoTo eh
'Code to Start timer
StartTime = Timer
'Code to make final report sheet visible and launch sheet hidden
Sheet1.Visible = True
Sheet1.Activate
Sheets("Sheet3").Visible = False
'declaring existing open workbook's name
MyBook = ActiveWorkbook.Name
'Code to cycle through all files in folder and paste values into master report
strFil = Dir(strFolder & "\*.csv*")
Do While strFil <> vbNullString
Set Wb = Workbooks.Open(strFolder & "\" & strFil)
Call RollMap_Ensocoat(Wb)
Workbooks(MyBook).Activate
ActiveSheet.Paste
Selection.HorizontalAlignment = xlCenter
ActiveCell.Offset(1).Select
Wb.Close SaveChanges:=False
strFil = Dir
Loop
'Formatting of values in final report
Range("B:I").NumberFormat = "#,##0"
Range("J:K").NumberFormat = "0.000"
Range("L:L").NumberFormat = "0.00"
Range("P:P").NumberFormat = "dd/MM/yyyy"
Range("Q:Q").NumberFormat = "hh:mm"
'Code to add header data to report (i.e. total files, name of person who created report, date and time report was created)
Range("Y2").Value = Now
Range("H2").Value = "# of Files Reported on: " & xCount
Range("P2").Value = Application.UserName
'Re-enabling features disabled for improved macro performance that are now needed to display finished report
Application.EnableEvents = True
Application.ScreenUpdating = True
'Code to refresh sheet so that graphs display properly
ThisWorkbook.RefreshAll
'Code to automatically save report in folder where files are located. Overrides warning prompting user that file is being saved in Non-macro enabled workbook.
Application.DisplayAlerts = False
ActiveWorkbook.SaveAs Filename:=strFolder & "\" & "Summary Report", FileFormat:=xlOpenXMLWorkbook
Application.DisplayAlerts = True
'Code to display message box letting user know the number of files reported on and the time taken.
SecondsElapsed = Round(Timer - StartTime, 2)
MsgBox "Operation successfully performed on " & xCount & " files in " & SecondsElapsed & " seconds." & vbNewLine & vbNewLine & "Report created at location: " & Application.ActiveWorkbook.FullName, vbInformation
Done:
Exit Sub
eh:
MsgBox "No Folder Selected. Please select re-select a board grade"
End Sub
Sub RollMap_Ensocoat(Wb As Workbook)
Dim ws As Worksheet
Dim finalRow As Long
'Set name of first sheet in spreadsheet to "1"
With Wb.Sheets(1)
.Name = "1"
.Range("H1").Formula = "=TCount(G3:G10000)"
End With
'Code to delete all rows that contain a "T" in column G" (Indicating a tab was fired and thus is waste)
With Sheets("1")
finalRow = .Range("G" & Rows.Count).End(xlUp).Row
.AutoFilterMode = False
With .Range("G4:G" & finalRow)
.AutoFilter Field:=1, Criteria1:="T"
.Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow.Delete
End With
.AutoFilterMode = False
'Code to calculate all the important values of each reel that will be pasted into the master report.
End With
Set ws = Wb.Sheets.Add(After:=Sheets(Wb.Sheets.Count))
With ws
.Range("A3").FormulaR1C1 = "=MAX('1'!C)"
.Range("B3").Formula = "=A3*I3"
.Range("C3").Formula = "=SUBTOTAL(109,'1'!B4:B10000)"
.Range("D3").Formula = "=SUBTOTAL(109,'1'!C4:C10000)"
.Range("E3").Formula = "=SUBTOTAL(109,'1'!D4:D10000)"
.Range("F3").Formula = "=SUBTOTAL(109,'1'!E4:E10000)"
.Range("G3").Formula = "=SUBTOTAL(109,'1'!F4:F10000)"
.Range("H3").Formula = "=SUM(C3:G3)"
.Range("I3").Formula = "='1'!A1"
.Range("J3").Formula = "=H3/(A3*I3)"
.Range("K3").Value = "0.21"
.Range("L3").Value = Wb.Name
.Range("M3").Formula = "=Left(L3, Len(L3) - 4)"
.Range("M3").Copy
.Range("M3").PasteSpecial xlPasteValues
.Range("N3").Formula = "=RIGHT(M3, 11)"
.Range("O3").Formula = "=LEFT(N3,2) & ""/"" & MID(N3,3,2) & ""/20"" & MID(N3,5,2)"
.Range("P3").Formula = "=MID(N3,8,2)& "":"" & MID(N3,10,2)"
.Range("Q3").Formula = "=Left(L3, Len(L3) - 16)"
.Range("R3").Formula = "='1'!H1"
.Range("A3:R3").Copy
.Range("A3:R3").PasteSpecial xlPasteValues
Application.CutCopyMode = False
.Range("A3:R3").Copy
End With
End Sub
Function TCount(rng As Range)
Const GAP_SIZE As Long = 5 '<< low number for testing...
Dim rv As Long, i As Long, fCount As Long, n As Long, d
Dim haveT As Boolean
rv = 0
d = rng.Value
n = UBound(d, 1)
fCount = 0
If n > GAP_SIZE Then
For i = 1 To n
If d(i, 1) = "T" Then
fCount = 0
haveT = True
Else
fCount = fCount + 1
If fCount = GAP_SIZE And haveT Then
rv = rv + 1
haveT = False
End If
End If
Next i
End If
TCount = rv
End Function
Something like this.
You may need to adjust if I made wrong assumptions about your rules.
Function TCount(rng As Range)
Const GAP_SIZE As Long = 5 '<< low number for testing...
Dim rv As Long, i As Long, fCount As Long, n As Long, d
Dim haveT As Boolean, earlyT as Boolean
rv = 0
d = rng.Value
n = UBound(d, 1)
fCount = 0
If n > GAP_SIZE Then
For i = 1 To n
If d(i, 1) = "T" Then
fCount = 0
If i <= GAP_SIZE Then earlyT = True '<<EDIT
haveT = True
Else
fCount = fCount + 1
If fCount = GAP_SIZE And haveT Then
rv = rv + 1
haveT = False
End If
End If
Next i
End If
TCount = rv - IIf(earlyT, 1, 0) '<< EDIT
End Function

vba only add new series to the chart if it does not exist

I have several sheets in my workbook which contains data to plot, every time I run a new analysis a new sheet is generated.
On my first sheet I plot all the data in the same graph, so to avoid re plotting all the series every time I append a new sheet I would like to just add a new series.
I thought that should be simple, but it is not for two reasons: When I first create the chart it adds somewhere between 1 and 9 series automatically:
Set myChart = ws.Shapes.AddChart.Chart
myChart.ChartType = xlXYScatterLinesNoMarkers
why does this generate any random series?
also if I delete the graph because I want to rerun one analysis, the graph will then be called 2 and so on... So I tried to give it a name and refer to its name instead, however that does not work:
Set myChart = ws.ChartObjects(ws.Name)
So in the first sheet(Orginal) I plot all data in the workbook, and in the rest I just plot the data for the current sheet as seen below. I use the same code function for both cases, where i just pass the argument all as true(orginal sheet) or false(sheet 1.....300)
Below is the code:
Sub createChart(ws As Worksheet, Optional all As Boolean = False)
Dim lastRow As Long
Dim myChart As Chart
Dim temp As Integer
Dim n As Integer
On Error Resume Next
' Delete the charts, just in case
If ws.ChartObjects.Count > 0 Then ' And Not all Then
ws.ChartObjects.Delete
End If
'If ws.ChartObjects.Count = 0 Then
Set myChart = ws.Shapes.AddChart.Chart
myChart.Name = ws.Name
'Else
'Set myChart = ws.ChartObjects(ws.Name) '''Fails why commented out
'End If
myChart.ChartType = xlXYScatterLinesNoMarkers
myChart.SetElement (msoElementPrimaryCategoryGridLinesMinor)
myChart.SetElement (msoElementPrimaryValueGridLinesMinorMajor)
myChart.SetElement (msoElementLegendBottom)
myChart.SetElement (msoElementChartTitleCenteredOverlay)
myChart.Parent.width = 800 ' px width graph
myChart.Parent.height = 500 ' px height graph
' it adds mysterious sometimes several random series, so we need to delete those that does not match sheet name
For n = myChart.SeriesCollection.Count To 0 Step -1
If Not SheetExists(myChart.SeriesCollection(n).Name) Then
myChart.SeriesCollection(n).Delete
End If
Next n
'*******************************************************************
'**************** FIRST PAGE CHART *********************************
'*******************************************************************
If all Then
Dim wsOther As Worksheet
Dim i As Integer
Dim fixRange As Boolean
Dim skipGraph As Boolean
fixRange = True
myChart.HasLegend = True
myChart.Legend.Position = xlLegendPositionRight
myChart.Parent.Top = 120
myChart.Parent.Left = 450
For Each wsOther In ThisWorkbook.Worksheets
If wsOther.Name <> ws.Name Then
lastRow = getLastRow(wsOther, 1)
skipGraph = False
'******* we only add graphs if it is not before ******************
If myChart.SeriesCollection.Count > 0 Then
For n = myChart.SeriesCollection.Count To 1 Step -1
If myChart.SeriesCollection(n).Name = wsOther.Name Then
skipGraph = True
Exit For
End If
Next n
End If
If Not skipGraph Then
With myChart.SeriesCollection.NewSeries
.Values = "=" & wsOther.Name & "!$E$2:$E$" & lastRow
.Name = wsOther.Name
.XValues = "=" & wsOther.Name & "!$B$2:$B$" & lastRow
End With
End If
If fixRange Then
' Range on axis
myChart.Axes(xlPrimary).MinimumScale = CDate(Application.WorksheetFunction.Min(Range(wsOther.Name & "!$B$2:$B$" & lastRow).Value2))
myChart.Axes(xlPrimary).MaximumScale = CDate(Application.WorksheetFunction.Max(Range(wsOther.Name & "!$B$2:$B$" & lastRow).Value2))
myChart.Axes(xlValue, xlPrimary).ScaleType = xlLogarithmic
fixRange = False
End If
End If
Next
'*******************************************************************************************************
'****************** SINGLE CHART ***********************************************************************
'*******************************************************************************************************
Else
myChart.HasLegend = False
myChart.Parent.Top = 40
myChart.Parent.Left = 300
lastRow = getLastRow(ws, 1)
With myChart.SeriesCollection.NewSeries
.Values = "=" & ws.Name & "!$E$2:$E$" & lastRow
.XValues = "=" & ws.Name & "!$B$2:$B$" & lastRow
End With
' Range on axis
myChart.Axes(xlPrimary).MinimumScale = CDate(Application.WorksheetFunction.Min(Range(ws.Name & "!$B$2:$B$" & lastRow).Value2))
myChart.Axes(xlPrimary).MaximumScale = CDate(Application.WorksheetFunction.Max(Range(ws.Name & "!$B$2:$B$" & lastRow).Value2))
End If
' *********************************************************************
' ******************* Sizing ******************************************
' *********************************************************************
With myChart.PlotArea
temp = .Top
temp = .height
.Top = 70
.height = 420
End With
'really dirty and crappy formatting of title
myChart.ChartTitle.Text = "Faraday Torr"
'X axis name
myChart.Axes(xlCategory, xlPrimary).HasTitle = True
myChart.Axes(xlCategory, xlPrimary).AxisTitle.Characters.Text = "Time [s]"
'y-axis name
myChart.Axes(xlValue, xlPrimary).HasTitle = True
myChart.Axes(xlValue, xlPrimary).AxisTitle.Characters.Text = "Pressure[Torr]"
Set myChart = Nothing
Set wsOther = Nothing
ws.Select
ws.Range("A1").Select
End Sub

VBA Macro to automate histogram throwing error 400

I wrote a macro to produce a histogram, given a certain selection. The code for the macro looks like this
Sub HistogramHelper(M As Range)
Dim src_sheet As Worksheet
Dim new_sheet As Worksheet
Dim selected_range As Range
Dim r As Integer
Dim score_cell As Range
Dim num_scores As Integer
Dim count_range As Range
Dim new_chart As Chart
Set selected_range = M
Set src_sheet = ActiveSheet
Set new_sheet = Application.Sheets.Add(After:=src_sheet)
title = selected_range.Cells(1, 1).Value
new_sheet.Name = title
' Copy the scores to the new sheet.
new_sheet.Cells(1, 1) = "Data"
r = 2
For Each score_cell In selected_range.Cells
If Not IsNumeric(score_cell.Text) Then
'MsgBox score_cell.Text
Else
new_sheet.Cells(r, 1) = score_cell
End If
r = r + 1
Next score_cell
num_scores = selected_range.Count
'Creates the number of bins to 5
'IDEA LATER: Make this number equal to Form data
Dim num_bins As Integer
num_bins = 5
' Make the bin separators.
new_sheet.Cells(1, 2) = "Bins"
For r = 1 To num_bins
new_sheet.Cells(r + 1, 2) = Str(r)
Next r
' Make the counts.
new_sheet.Cells(1, 3) = "Counts"
Set count_range = new_sheet.Range("C2:C" & num_bins + 1)
'Creates frequency column for all counts
count_range.FormulaArray = "=FREQUENCY(A2:A" & num_scores + 1 & ",B2:B" & num_bins & ")"
'Make the range labels.
new_sheet.Cells(1, 4) = "Ranges"
For r = 1 To num_bins
new_sheet.Cells(r + 1, 4) = Str(r)
new_sheet.Cells(r + 1, 4).HorizontalAlignment = _
xlRight
Next r
' Make the chart.
Set new_chart = Charts.Add()
With new_chart
.ChartType = xlBarClustered
.SetSourceData Source:=new_sheet.Range("C2:C" & _
num_bins + 1), _
PlotBy:=xlColumns
.Location Where:=xlLocationAsObject, _
Name:=new_sheet.Name
End With
With ActiveChart
.HasTitle = True
.HasLegend = False
.ChartTitle.Characters.Text = title
.Axes(xlCategory, xlPrimary).HasTitle = True
.Axes(xlCategory, _
xlPrimary).AxisTitle.Characters.Text = "Scores"
.Axes(xlValue, xlPrimary).HasTitle = True
.Axes(xlValue, xlPrimary).AxisTitle.Characters.Text _
_
= "Out of " & num_scores & " responses"
' Display score ranges on the X axis.
.SeriesCollection(1).XValues = "='" & _
new_sheet.Name & "'!R2C4:R" & _
num_bins + 1 & "C4"
End With
ActiveChart.SeriesCollection(1).Select
With ActiveChart.ChartGroups(1)
.Overlap = 0
.GapWidth = 0
.HasSeriesLines = False
.VaryByCategories = False
End With
r = num_scores + 2
new_sheet.Cells(r, 1) = "Average"
new_sheet.Cells(r, 2) = "=AVERAGE(A1:A" & num_scores & _
")"
r = r + 1
new_sheet.Cells(r, 1) = "StdDev"
new_sheet.Cells(r, 2) = "=STDEV(A1:A" & num_scores & ")"
End Sub
I am currently using a WorkBook that looks like this:
Eventually, I want to produce a macro that automatically iterates over each column, calling the Histogram Helper function with each column, producing multiple histograms over multiple worksheets. For now, I'm just trying to test putting in TWO ranges into HistogramHelper, like so:
Sub GenerateHistograms()
HistogramHelper Range("D3:D30")
HistogramHelper Range("E3:E30")
End Sub
However, upon running the Macro, I get a dialog box with the error number 400, one of the sheets is produced successfully with the worksheet title Speaker, and another sheet is produced with a numerical title and no content.
What is going on?
Edit: The workbook in question: https://docs.google.com/file/d/0B6Gtk320qmNFbGhMaU5ST3JFQUE/edit?usp=sharing
Edit 2- Major WTF?:
I switched the beginning FOR block to this for debugging purposes:
For Each score_cell In selected_range.Cells
If Not IsNumeric(score_cell.Text) Then
MsgBox score_cell.Address 'Find which addresses don't have numbers
Else
new_sheet.Cells(r, 1) = score_cell
End If
r = r + 1
Next score_cell
Whenever you run this, no matter which range you put as the second Macro call (in this case E3:E30) the program prints out that each cell $E$3- $E$30 is a non-text character. Why oh why?
Don't you need this?
Sheets(title).Activate
TIP: for this kind of recursive implementations implying many creations/deletions and getting every day more and more complex, I wouldn't ever rely on "Active" elements (worksheet, range, etc.), but in specific ones (sheets("whatever")) avoiding problems and easing the debugging.
------------------------ UPDATE
No, apparently, you don't need it.
Then, update selected_range.Cells(1, 1).Value such that it takes different values for each new worksheet, because this is what is provoking the error: creating two worksheets with the same name.
------------------------ UPDATE 2 (after downloading the spreadsheet)
The problem was what I thought: two worksheets created with the same name (well... not exactly: one of the spreadhsheets was intended to be called after a null variable). And the reason for this problem, what I thought too: relying on "Active elements". But the problem was not while using the ActiveSheet, but while passing the arguments: the ranges are given without spreadsheet and were taken from the last created spreadsheet. Thus, solution:
HistogramHelper Sheets("Sheet1").Range("D3:D30")
HistogramHelper Sheets("Sheet1").Range("E3:E30")
Bottom line: don't rely on "Active"/not-properly-defined elements for complex situations.

Inefficient code that doesn't find matching data values

I have 3 issues with the following piece of code:
Intention of code: I have a table of data, 4 columns (F,G, H and I) wide and X rows long (X is typically between 5 and 400). I have a list of dates in column M, typically no more than 8 dates. Column H of table, contains dates as well. I want to find the dates that are in both columns (H and M) and whenever they appear, go to the same row in column I and set its value to zero, and the one after it (so if a match was in H100, then I100 and I101 would be zeroed).
issues with code: edited 1) as per feedback.
1) I have, using an if formula (=if(H100=M12,1,0), verified that there is one match, as how the spreadsheet sees it. The macro does not find this match, despite confirmation from the if formula. Cells I100 and I101 have nonzero values, when they should be zeroed.
2) the code runs, but takes about 3 minutes to go through 3 sheets of 180 rows of data. What can be done to make it run faster and more efficiently? It could have up to 30 sheets of data, and 400 rows (extreme example but possible, in this instance im happy to let it run a bit).
3) Assuming my data table before the macro is run, is 100 rows long, starting in row 12, after the macro, column I has nonzero values for 111 rows, and zeroes for the next 389. Is there a way I can prevent it from filling down zeroes, and leaving it blank?
I am using a correlate function afterwards on column I and there huge agreement of 0's with 0's is distorting this significantly. Thanks in advance,
Sub DeleteCells()
Dim ws As Worksheet
Dim cell As Range, search_cell As Range
Dim i As Long
Dim h As Long
Application.ScreenUpdating = False
For Each ws In ThisWorkbook.Worksheets
If Not ws.Name = "Cover" Then
For Each cell In ws.Range("H12:H500")
On Error Resume Next
h = ws.Range("G" & Rows.Count).End(xlUp).Row
i = ws.Range("L" & Rows.Count).End(xlUp).Row
Set search_cell = ws.Range("M12:M" & h).Find(what:=cell.Value, LookIn:=xlValues, lookat:=xlWhole)
On Error GoTo 0
If Not search_cell Is Nothing Then
ws.Range("I" & cell.Row).Value = 0
ws.Range("I" & cell.Row + 1).Value = 0
Set search_cell = Nothing
End If
Next cell
End If
Next ws
Application.ScreenUpdating = True
Set ws = Nothing: Set cell = Nothing: Set search_cell = Nothing
End Sub
EDIT: TESTED CODE, will work for 0, 1 row of data in H/M column starting from row 12?
EDIT: Updated the cell to handle case with 1 line of data, untested :|
I will give my solution first, this one should be much faster because it read the cells into memory first
Please comment if it doesn't work or you have further question
Sub DeleteCells()
Dim ws As Worksheet
Dim i As Long
Dim h As Long
Dim MColumn As Variant ' for convinence
Dim HColumn As Variant
Dim IColumn As Variant
Application.ScreenUpdating = False
For Each ws In ThisWorkbook.Worksheets
If Not ws.Name = "Cover" Then 'matching the target sheet
' matching the rows where column M's date matches column H's date
'starting row num is 12
With ws ' for simplifying the code
h = .Range("H" & .Rows.count).End(xlUp).Row
If h = 12 Then ' CASE for 1 row only
If Range("H12").Value = Range("M12").Value Then
Range("I12:I13").Value = ""
End If
ElseIf h < 12 Then
' do nothing
Else
ReDim HColumn(1 To h - 11, 1 To 1)
ReDim MColumn(1 To h - 11, 1 To 1)
ReDim IColumn(1 To h - 10, 1 To 1)
' copying the data from worksheet into 2D arrays
HColumn = .Range("H12:H" & h).Value
MColumn = .Range("M12:M" & h).Value
IColumn = .Range("I12:I" & h + 1).Value
For i = LBound(HColumn, 1) To UBound(HColumn, 1)
If Not IsEmpty(HColumn(i, 1)) And Not IsEmpty(MColumn(i, 1)) Then
If HColumn(i, 1) = MColumn(i, 1) Then
IColumn(i, 1) = ""
IColumn(i + 1, 1) = ""
End If
End If
Next i
'assigning back to worksheet cells
.Range("H12:H" & h).Value = HColumn
.Range("M12:M" & h).Value = MColumn
.Range("I12:I" & h + 1).Value = IColumn
End If
End With
End If
Next ws
Application.ScreenUpdating = True
End Sub