Setting graph series in excel VBA with Dictionary Keys - vba

Objective:
Dynamically generating a (100% Stacked) graph based on data in a spreadsheet.
Conditions:
I have a list sites with repetitive milestones (each site uses the same 4 milestones, but the milestones differ between projects. This functionality will be used in the trackers for several projects).
Current State:
It's drawing the stacked barchart as desired, but I cant seem to get the legend (series) to be renamed to the unique keys in the dictionary that is being built from the identified milestones.
Data Setup:
Columns X3 and beyond has the list of milestones. there are 40 records (2 blank lines) with 4 unique values. The d1 dictionary contains the unique 4 values as displayed by the output into column R (for testing only).
Image: List of data and location/milestones
All code pertaining to drawing the graph:
With Worksheets("Sheet1")
.Columns.EntireColumn.Hidden = False 'Unhide all columns.
.Rows.EntireRow.Hidden = False 'Unhide all rows.
.AutoFilterMode = False
lastrow = Range("W" & Rows.Count).End(xlUp).Row
'If MsgBox("Lastrow is: " & lastrow, vbYesNo) = vbNo Then Exit Sub
End With
Dim MyLocationCount As Integer
Dim MyMilestoneCount As Integer
'Use VbA code to find the unique values in the array with locations.
'GET ARRAY OF UNIQUE LOCATIONS
Worksheets("Sheet1").Range("W3:W" & lastrow).Select
Dim d As Object, c As Range, k, tmp As String
Set d = CreateObject("scripting.dictionary")
For Each c In Selection
tmp = Trim(c.Value)
If Len(tmp) > 0 Then d(tmp) = d(tmp) + 1
Next c
For Each k In d.Keys
Debug.Print k, d(k)
MyLocationCount = MyLocationCount + 1
Next k
Range("U1:U" & d.Count) = Application.Transpose(d.Keys) '<-- For verification of the locations keys only.
'MsgBox (MyLocationCount)
'SET ARRAY CATEGORY VALUES
Dim d3 As Object
Set d3 = CreateObject("scripting.dictionary")
x = 0
Do
x = x + 1
d3.Add key:=x, Item:=1
'MsgBox "Key " & x & ": " & d3(x) & " Key Count: " & d3.Count
Loop Until x = MyLocationCount
Dim k3 As Variant
For Each k3 In d3.Keys
' Print key and value
Debug.Print k3, d3(k3)
Next
'------------
Range("T1:T" & d3.Count) = Application.Transpose(d3.Items)'<-- For verification of the locations items only.
'GET ARRAY OF UNIQUE MILESTONES
Worksheets("Sheet1").Range("X3:X" & lastrow).Select
Dim d1 As Object, c1 As Range, k1, tmp1 As String
Set d1 = CreateObject("scripting.dictionary")
For Each c1 In Selection
tmp1 = Trim(c1.Value)
If Len(tmp1) > 0 Then d1(tmp1) = d1(tmp1) + 1
Next c1
For Each k1 In d1.Keys
Debug.Print k1, d1(k1)
MyMilestoneCount = MyMilestoneCount + 1
Next k1
Range("R1:R" & d1.Count) = Application.Transpose(d1.Keys) '<-- For verification of the milestone keys only.
ActiveSheet.ChartObjects("Chart 2").Activate
'Delete all current series of data.
Dim n As Long
With ActiveChart
For n = .SeriesCollection.Count To 1 Step -1
.SeriesCollection(n).Delete
Next n
End With
'==== START PROBLEM AREA =====
'Loop the XValues and Values code as many times as you have series. make sure to increment the collection counter. Use array values to hardcode the categories.
x = 0
Do Until x = MyMilestoneCount
With ActiveChart.SeriesCollection.NewSeries
.XValues = Array(d.Keys)
.Values = Array(d3.Items)
x = x + 1
End With
'NAME MILESTONE
'MsgBox (d1.keys(x))
ActiveChart.FullSeriesCollection(x).Name = "=""Milestone " & x & """" '<==== THIS WORKS BUT IS NOT DESIRED.
'ActiveChart.FullSeriesCollection(x).Name = d1.Keys(x) '<==== THIS IS WHAT IM TRYING TO GET TO WORK.
Loop
'==== END PROBLEM AREA =====
ActiveChart.Location Where:=xlLocationAsObject, Name:="Sheet1"
'SET LEGEND SIZE
ActiveChart.Legend.Select
Selection.Left = 284.71
Selection.Width = 69.289
Selection.Height = 144.331
Selection.Top = 9.834
Selection.Height = 157.331
With ActiveSheet.ChartObjects("Chart 2").Chart.Axes(xlValue, xlPrimary)
'.Border.LineStyle = xlNone
.MajorTickMark = xlNone
.MinorTickMark = xlNone
.TickLabelPosition = xlNone
End With
End Sub
Anyone any idea on how to use the d1 keys instead of the manual naming? (See the <=== arrows).
I have code on how to color each section of the barchart based on the data that is determined in the spreadsheet (see image). right now my main challenge is getting the series properly named.
Thanks and have a great day!
Okki

Related

Create Sub array variable name sheet

I have one array with Sheet Names called SheetNames and I want to generate a sub array of it that only returns True at the condition (IF). I try to have a loop into a cell value onto different sheets, evaluating condition cell.value = "S". When checks that for the first D column (z = 4) I want to make the same check (IF condition) for columns D to DR at the same row.
I need to get similar result if I use formula at
Diary!C7
= IF (element!D6 = "S",CONCATENATE (element!B1, ", "), ""),
IF (element1!D6 = "S",CONCATENATE (element1!B1, ", "), ""), ....
IF (element!E6 = "S",CONCATENATE (element!B1, ", "), ""),
IF (element1!E6 = "S",CONCATENATE (element1!B1, ", "), "") .... )
Where element is a sheet name taken from an array with the sheet names who get the condition (Code S or another code).
SheetNames is one array with all the book sheets and FSheet (Filtered Sheet with condition) an array with only the filtered (with condition IF). When I can populate FSheet array for each sheet I test the condition then I must concatenate it's values at another sheet/cell and began the test condition again to the next cell (E6) ... But I'm trapped at the step to create FSheet.
Sub Test()
Dim ws As Worksheet
Dim SheetNames() As String, FSheets() As String, q As String
Dim element As Variant
Dim lastSheet As Integer, r As Integer, incrSheet As Integer, i As Integer
Dim Rgn As Range
' Enter the sheet names into an array. Redim array's size to the number of sheets (lastSheet)
For Each ws In ActiveWorkbook.Worksheets
ReDim Preserve SheetNames(lastSheet)
SheetNames(lastSheet) = ws.name
lastSheet = lastSheet + 1
Next ws
MsgBox lastSheet
' Test condition for each sheet/cell
For z = 4 To 11
For Each element In SheetNames()
incrSheet = 1
If ActiveWorkbook.Sheets(element).Cells(6, z).Value = "S" Then
ReDim Preserve FSheets(incrSheet)
FSheets(incrSheet) = element
incrSheet = incrSheet + 1
End If
Next element
Next z
i = 3
' Define the sheet to work (total project will have more than one, one for code we need test, S, C, etc)
With Worksheets("Diary")
.Activate
.Range("C7").Select
' Concatenate values at Summary page
Do
Cells(7, i).Select
For r = 1 To UBound(FSheets)
'Concatenate with &:
varConctnt = varConctnt & ", " & FSheets(r)
Next r
'remove the "&" before the first element:
varConctnt = Mid(varConctnt, 2)
q = varConctnt
varConctnt = ""
i = i + 1
ActiveCell.Value = q
Loop While i < 11
' Drag the formula for the rest of the rows
Range("C7:J7").Select
Selection.AutoFill Destination:=Range("C7:J12"), Type:=xlFillDefault
End With
End Sub
Where you are going wrong, is your attempt to dynamically set the range. Assuming you are testing the value of a single cell, it is much easier to use Cells, rather than Range, since you can use R1C1 notation. Try something like this:
incrSheet = 1
For z = 4 To 11
For Each element In SheetNames()
If ActiveWorkbook.Sheets(element).Cells(6, z).Value = "S" Then
ReDim Preserve FSheets(incrSheet)
FSheets(incrSheet) = element
MsgBox incrSheet
incrSheet = incrSheet + 1
End If
Next element
Next z

Concatenate Strings with Do Loop with dynamic column and files

So at Assign sheet I indicate the sheets to take data for each group (each for column and the first row is the explanation of the group). I dynamically can add res at the file or delete
After I use predefined codes to assign which type of discounts / day are applied. At the example I only put two codes (C and S) and one week. For example the raw sheet data for designations Red and Black.
Data product worksheet
Then at Diary I want to show the result of concatenate the B1 value (name of product) each time code fromm price are indicated into the rows. Also I use two loop because at raw product data I have one column for price but at the Diary I have two
Summary page
This is what I finally want to get and doing like that because my boss don't know anything for code and he wan't edite it so I try to do ll dynamic at the sheets :) [I only put two images because i don't have reputations point enough to put more]
With the formula I only get FALSE as answer :(, and I need to get what you can see at summary page
Sub Diary()
Dim I As Integer, x As Integer, y As Integer, z As Integer, n As Integer
Dim p As Integer, d As Integer, f As Integer
Dim a As String, b As String
Dim element As Variant
' Initialize variables I and y at 3 and 4 to begin to show the data at the column I desire. Also x and z were intended to pass the one column mode data sheet to the two column mode at the summary page.
I = 3
x = 1
y = 4
z = 0
With Worksheets("Asign")
.Activate
.Range("B2").Select
End With
' Set the size of Data with sheet names it get form the page assign. It can dynamically changed as size as names of sheets
With ActiveSheet
r = .Cells(.Rows.Count, "B").End(xlUp).Row
End With
Dim Data() As String
ReDim Data(r)
For p = 1 To r - 1
Data(p) = ActiveSheet.Cells(p + 1, "B").Value
Next p
With Worksheets("Diary")
.Activate
.Range("C7").Select
End With
' At Diary concatenate the same cell for all the sheets I have his name stored at Data() and then pass to the next cell with data at raw data sheets (in the images (Red, Black ,... pages). In this case search for code S
Do
Cells(7, I).Select
ActiveCell.Value = ActiveCell.FormulaR1C1 = "=CONCATENATE(" & a & ")"
For Each element In Data
b = ActiveCell.FormulaR1C1 = "IF(Data& !R[-2]C[" & x & "]=""S"",CONCATENATE(Data&!R1C2,"", ""),"""")"
a = b & ";" & b
Next element
x = x - 1
I = I + 2
Loop While I < 4
' The same for the second column of summary sheet called Diary. In this case search for code C
Do
Cells(7, y).Select
ActiveCell.Value = ActiveCell.FormulaR1C1 = _
"=CONCATENATE(" & a & ")"
For Each element In Data
b = ActiveCell.FormulaR1C1 = "IF(Data& !R[-2]C[" & z & "]=""S"",CONCATENATE(Data&!R1C2,"", ""),"""")"
a = b & ";" & b
Next element
z = z - 1
y = y + 2
Loop While I < 4
' Drag and Drop the formula to all the sheet's cells you need
Range("C8:E8").Select
Selection.AutoFill Destination:=Range("C8:E10"), Type:=xlFillDefault
'
End Sub
try this. .... could be simplified by looping through "colors" .... black, red, etc
Sub Diary()
Dim red As Variant
red = Sheets("red").Range("d6:g12") ' put range data into an array for processing
Dim black As Variant
black = Sheets("black").Range("d6:g12")
Dim i As Integer
Dim j As Integer
Dim strC As String
Dim strS As String
For i = 1 To 7
For j = 1 To 4
strC = ""
strS = ""
If LCase(black(i, j)) = "c" Then strC = "Black"
If LCase(black(i, j)) = "s" Then strS = "Black"
If LCase(red(i, j)) = "c" Then
If Len(strC) > 1 Then strC = strC & ";"
strC = strC & "Red"
End If
If LCase(red(i, j)) = "s" Then
If Len(strS) > 1 Then strS = strS & ";"
strS = strS & "Red"
End If
Sheets("diary").Range("c7").Cells(i, j * 2 - 1) = strS
Sheets("diary").Range("c7").Cells(i, j * 2) = strC
Next j
Next i
End Sub

Excel VBA Range.Rows iterator order of rows processed

I need to traverse a range from the bottom of the spreadsheet to the top of the spreadsheet. The range can be discontinuous, but I have removed overlaps (I'm only concerned with the row order, so I've also reduced the column to "A") and placed the range in "Overall_Range". Since the areas can come into the range in any order, I've built a function, Get_Loop_Order, that returns an array with order in which the areas should be processed to go from bottom to top. My plan was to just iterate over each area (from bottom to top) like this:
Loop_Order = Get_Loop_Order(Overall_Range)
For A = LBound(Loop_Order) To UBound(Loop_Order)
For Each this_row In Overall_Range.Areas(Loop_Order(A)).Rows
... do stuff ...
Next this_row
Next A
I realized that the For Each on Range.Rows will not be processed in reverse order (in fact, I have no guarantee of the order at all as far as I know).
Does anyone know if there is a way to loop through a range that is guaranteed to occur in a specific row order? When I select (the use of the word "select" here should not be confused with the Excel VBA term "Selection," the code above uses "Overall_Range") a range from bottom to top (A10:A2) the loop is in that order, when I select a range from top to bottom (A2:A10) it is in that order. I have no idea what happens if I do something like Union(A10:A2, A1:A2). I'm thinking that I will have to write another function that returns an array with the order to process things, but I'd love it if someone else had another solution. Can you help?
UPDATE:
I did some more testing Here is the code:
Dim my_range As Range 'Range being tested
Dim N As Long 'Loop variable when numbers are needed
Dim M As Range 'Loop variable when ranges are needed
Set my_range = ActiveSheet.Range("A2:A10")
ActiveSheet.Range("B1").Value = "A2:A10"
ActiveSheet.Range("B1").Font.Bold = True
ActiveSheet.Range("B1").HorizontalAlignment = xlCenter
ActiveSheet.Range("B1:C1").Merge
ActiveSheet.Range("B2").Value = "Row Index"
ActiveSheet.Range("B2").Font.Bold = True
ActiveSheet.Range("B2").HorizontalAlignment = xlCenter
ActiveSheet.Range("C2").Value = "Row Iterator"
ActiveSheet.Range("C2").Font.Bold = True
ActiveSheet.Range("C2").HorizontalAlignment = xlCenter
For N = 1 To my_range.Rows.Count
ActiveSheet.Range("B" & N + 2).Value = my_range.Rows(N).Row
Next N
N = 1
For Each M In my_range.Rows
ActiveSheet.Range("C" & N + 2).Value = M.Row
N = N + 1
Next M
Set my_range = ActiveSheet.Range("A10:A2")
ActiveSheet.Range("D1").Value = "A10:A2"
ActiveSheet.Range("D1").Font.Bold = True
ActiveSheet.Range("D1").HorizontalAlignment = xlCenter
ActiveSheet.Range("D1:E1").Merge
ActiveSheet.Range("D2").Value = "Row Index"
ActiveSheet.Range("D2").Font.Bold = True
ActiveSheet.Range("D2").HorizontalAlignment = xlCenter
ActiveSheet.Range("E2").Value = "Row Iterator"
ActiveSheet.Range("E2").Font.Bold = True
ActiveSheet.Range("E2").HorizontalAlignment = xlCenter
For N = 1 To my_range.Rows.Count
ActiveSheet.Range("D" & N + 2).Value = my_range.Rows(N).Row
Next N
N = 1
For Each M In my_range.Rows
ActiveSheet.Range("E" & N + 2).Value = M.Row
N = N + 1
Next M
Set my_range = Union(ActiveSheet.Range("A10:A2"), ActiveSheet.Range("A1:A2"))
ActiveSheet.Range("F1").Value = "UNION(A10:A2,A1:A2)"
ActiveSheet.Range("F1").Font.Bold = True
ActiveSheet.Range("F1").HorizontalAlignment = xlCenter
ActiveSheet.Range("F1:G1").Merge
ActiveSheet.Range("F2").Value = "Row Index"
ActiveSheet.Range("F2").Font.Bold = True
ActiveSheet.Range("F2").HorizontalAlignment = xlCenter
ActiveSheet.Range("G2").Value = "Row Iterator"
ActiveSheet.Range("G2").Font.Bold = True
ActiveSheet.Range("G2").HorizontalAlignment = xlCenter
For N = 1 To my_range.Rows.Count
ActiveSheet.Range("F" & N + 2).Value = my_range.Rows(N).Row
Next N
N = 1
For Each M In my_range.Rows
ActiveSheet.Range("G" & N + 2).Value = M.Row
N = N + 1
Next M
Set my_range = Union(ActiveSheet.Range("A10:A2"), ActiveSheet.Range("A1:A2"), ActiveSheet.Range("A11:A12"))
ActiveSheet.Range("H1").Value = "UNION(A10:A2,A13:A15,A11:A12)"
ActiveSheet.Range("H1").Font.Bold = True
ActiveSheet.Range("H1").HorizontalAlignment = xlCenter
ActiveSheet.Range("H1:I1").Merge
ActiveSheet.Range("H2").Value = "Row Index"
ActiveSheet.Range("H2").Font.Bold = True
ActiveSheet.Range("H2").HorizontalAlignment = xlCenter
ActiveSheet.Range("I2").Value = "Row Iterator"
ActiveSheet.Range("I2").Font.Bold = True
ActiveSheet.Range("I2").HorizontalAlignment = xlCenter
For N = 1 To my_range.Rows.Count
ActiveSheet.Range("H" & N + 2).Value = my_range.Rows(N).Row
Next N
N = 1
For Each M In my_range.Rows
ActiveSheet.Range("I" & N + 2).Value = M.Row
N = N + 1
Next M
The results are something that I cannot post because I cannot post images...sigh...they show that no matter how crazy the range is, when accessed via the Rows collection, they come in row order.
This seems to show that the rows are consistently returned in order no matter what crazy thing I do with the range if I access it via the Rows collection. I'm thinking that this means the approach of just stepping backwards through the range (as suggested in the comments) will work.
This code should do the trick.
With one clarification: From a VBA perspective Range("A2:A10") and Range("A10:A2") are exactly the same (i.e. they return the same address: $A$2:$A$10). In order to loop one way or another, you'll need to pass another argument.
EDIT
It takes the Overall_Range that you provide, then the direction of Up or Down then assigns values to variables to use in the For statement.
No selections used.
Option Explicit
Sub LoopOrderTest()
Dim Overall_Range As Range
Dim sLoopDir As String
Dim iTtlRows As Integer
Dim iLoopStep As Integer
Dim iLoopFrom As Integer
Dim iLoopTo As Integer
Dim n As Integer
Set Overall_Range = Range("A2:A10")
sLoopDir = "Up" 'or "Down"
iTtlRows = Overall_Range.Rows.Count 'Get total rows
'Assign for loop control items based on sLoopDir value
If sLoopDir = "Up" Then
iLoopFrom = 1
iLoopTo = iTtlRows
iLoopStep = 1
ElseIf sLoopDir = "Down" Then
iLoopFrom = iTtlRows
iLoopTo = 1
iLoopStep = -1
End If
Dim i As Integer 'used only to put items in cells for testing
i = 1
For n = iLoopFrom To iLoopTo Step iLoopStep
'do stuff.
'for now just print a number showing the order that the loop works through
Overall_Range.Cells(n, 1).Value = i
i = i + 1
Next n
End Sub
This shows what happens when I set sLoopDir = "Up" and run the code. Numbers ascending indicate it loops from top to bottom.
This shows what happens when I set sLoopDir = "Down" and run the code. Numbers descending indicate it loops from bottom to top.

Excel VBA Find value in column, and do math

I have
Column A - with Names
Column B - with Quantities
Column C - Where I want returned Value of Quantity x Cost
Column E - with Names but located in different cells
Column F - with Prices
What I'm trying to achieve is: Take value from A1 and find it in E:E (Lets say we found it in E10), when found take value of B1 and multiply it by Respective value of F10 - and put all this in Column C
And so on for all values in column A
I was trying to do it with Do while and two variables x and y, but for some reason it doesn't find all values only for some rows.
Thank you in Advance.
Sub update_button()
'calculates money value for amazon sku
Dim x, y, z As Integer 'x, y, and z variables function as loop counters
'Loop through added SKU/Prices
For x = 4 To 25000
If Worksheets("Sheet1").Range("H" & x) = "" Then
'Blank row found, exit the loop
Exit For
End If
'Loop through Column E to find the first blank row to add the value from H into
For y = 4 To 25000
If Worksheets("Sheet1").Range("E" & y) = "" Then
'Blank row found, Add SKU and Price
Worksheets("Sheet1").Range("E" & y) = Worksheets("Sheet1").Range("H" & x)
Worksheets("Sheet1").Range("F" & y) = Worksheets("Sheet1").Range("I" & x)
'Blank out Columns H and I to prevent need to do it manually
Worksheets("Sheet1").Range("H" & x) = ""
Worksheets("Sheet1").Range("I" & x) = ""
Exit For
End If
Next y
Next x
'---NOW THIS IS WHERE I HAVE THE PROBLEM
'Get Values
Dim intCumulativePrice As Integer
'Loop through report tab and get SKU
x = 4 'initialize x to the first row of data on the Sheet1 tab
Do While Worksheets("Sheet1").Range("A" & x) <> "" 'Loop through valid SKU's to find price of item
y = 4 'initialize y to the first row of SKUs on the Sheet1 tab
Do While Worksheets("Sheet1").Range("E" & y) <> ""
If Worksheets("Sheet1").Range("E" & x) = Worksheets("Sheet1").Range("A" & y) Then 'Check if current SKU on Sheet1 tab matches the current SKU from SKU list
'Calculates the total
intCumulativePrice = intCumulativePrice + (Worksheets("Sheet1").Range("B" & y) * Worksheets("Sheet1").Range("F" & x))
' Puts Quantity X Price in Column B agains every Cell
Worksheets("Sheet1").Range("C" & y) = (Worksheets("Sheet1").Range("B" & y) * Worksheets("Sheet1").Range("F" & x))
Exit Do
End If
y = y + 1
Loop
x = x + 1
Loop
'Puts Grand total in Column L Cell 4
Worksheets("Sheet1").Range("L4") = intCumulativePrice
'Show messagebox to show that report processing has completed
MsgBox "Report processing has been completed successfully", vbInformation, "Processing Complete!"
End Sub
You can do this with a simple VLOOKUP formula in column C.
=VLOOKUP(A1,E1:F65000,2,FALSE)*B1
You can also use a named range for the data in columns E and F, so you don't have to rely on a fixed address like E1:F65000.
To do this with VBA you should copy the source data to Variant arrays and loop over those. Much faster and IMO easier to read and debug.
Something like this
Sub Demo()
Dim Dat As Variant
Dim PriceList As Range
Dim PriceListNames As Variant
Dim PriceListPrices As Variant
Dim Res As Variant
Dim sh As Worksheet
Dim i As Long
Dim nm As String
Dim nmIdx As Variant
Dim FirstDataRow As Long
FirstDataRow = 4
Set sh = ActiveSheet
With sh
Dat = Range(.Cells(FirstDataRow, "B"), .Cells(.Rows.Count, "A").End(xlUp))
Set PriceList = Range(.Cells(FirstDataRow, "E"), .Cells(.Rows.Count, "F").End(xlUp))
PriceListNames = Application.Transpose(PriceList.Columns(1)) ' Need a 1D array for Match
PriceListPrices = PriceList.Columns(2)
ReDim Res(1 To UBound(Dat, 1), 1 To 1)
For i = 1 To UBound(Dat, 1)
nm = Dat(i, 1)
nmIdx = Application.Match(nm, PriceListNames, 0)
If Not IsError(nmIdx) Then
Res(i, 1) = Dat(i, 2) * PriceListPrices(nmIdx, 1)
End If
Next
Range(.Cells(FirstDataRow, 3), .Cells(UBound(Dat, 1) + FirstDataRow - 1, 3)) = Res
End With
End Sub
Try this:
Sub HTH()
With Worksheets("Sheet1")
With .Range("A4", .Range("A" & Rows.Count).End(xlUp)).Offset(, 2)
.Formula = "=VLOOKUP(A4,E:F,2,FALSE)*$B$1"
.Value = .Value
End With
End With
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.