How can I create a chart from unique values in a range - excel

I have items that are being populated on a worksheet via userform. When I open the workbook I'm trying to get the tool to go to the sheet grab the data and generate a chart/dashboard on the main landing sheet.
In the range of data contains statuses. I want VBA to look through one column of data and create a chart that counts each different status and put that in a bar chart.
yaxis = the different statuses
xaxis = count
my code so far
Sub populatecharts()
Dim ws As Worksheet
Dim ch As Chart
Dim tablerng As Range
Dim rng1 As Range
Dim rng2 As Range
Dim rng3 As Range
Dim sh As String
Set ws = ActiveSheet
'When the workbook opens it should always check the data and populate the BA Dashboard
'I need to check for sheets and if they exist generate a chart from the data
sh = "Action"
On Error Resume Next
Worksheets("Action").Visible = True
If CheckSheetExist(sh) = False Then
GoTo nextchart1
Else
Worksheets(sh).Activate
'Set ws = ActiveSheet
Set rng1 = Range("G4", Range("G4", "G4").End(xlDown))
rng1.Select
'Set rng2 = Range("B2")
'Set rng3 = Range("C3")
'Set tablerng = rng1 '& rng2 & rng3
Set ch = ws.Shapes.AddChart2(Width:=200, Height:=200, Left:=Range("B4").Left, Top:=Range("B4").Top).chart
With ch
.SetSourceData Source:=rng1
.ChartType = xlBarClustered
.ChartTitle.Text = "Action Items by Status"
End With
ws.Activate
Worksheets("Action").Visible = False
End If
Seems easy but I'm not able to think through it, also the location is hit or miss even though I define the top and bottom and size. Sometimes it's to the right of the cell I chose to be the left.

Try the next way, please. It uses a dictionary to extract the unique values and their count and array to feed the necessary series. Try running it on active sheet and adapt it to your situation only after having the confirmation that what it returns is what you need:
Sub populatecharts()
Dim shT As Worksheet, ch As Chart, lastRow As Long
Dim arrY, arrX, i As Long, dict As Object
Set shT = ActiveSheet 'use here the sheet you need
lastRow = shT.Range("G" & shT.Rows.count).End(xlUp).row
arrX = shT.Range("G4:G" & lastRow).Value 'put the range in a array
Set dict = CreateObject("Scripting.Dictionary") 'needed for the next step
On Error Resume Next
shT.ChartObjects("MyChartXY").Delete 'for the case of re running need
On Error GoTo 0
For i = 1 To UBound(arrX)
If Not dict.Exists(arrX(i, 1)) Then
dict(arrX(i, 1)) = 1 'create the unique keys
Else
dict(arrX(i, 1)) = dict(arrX(i, 1)) + 1 'increment the key next occurrrence
End If
Next i
arrX = dict.Keys: arrY = dict.Items 'extract the necessary arrays
Set ch = shT.ChartObjects.Add(left:=shT.Range("B4").left, _
top:=shT.Range("B4").top, width:=200, height:=200).Chart
With ch
.ChartType = xlBarClustered
.HasTitle = True
.ChartTitle.Text = "Action Items by Status"
.SeriesCollection.NewSeries.Values = arrY 'feed it with the array elements
.SeriesCollection(1).XValues = arrX 'feed it with the array elements
End With
End Sub
Please, test it and send some feedback.

Related

Create a graph of a column with gaps between data

I'm trying to create graphs of every column of data in a worksheet. As of right now it works as long as there are no gaps in the column of data, but I need it to be robust enough that it works if there are gaps in the data. The data is entered in batches with different columns having different lengths due to different measurement requirements. Each of the rows will also have an identifier in the first column indicating what batch of data that row comes from (see attached file). Since the identifier column will be the same length as the longest data column, I compare the last row of that to the bottom row of any given data column to make sure all the data is getting graphed. However right now the it gets stuck in the loop if there's a gap in the data.
Sub GraphAllColumns()
Dim col As Range 'The cell at the top of the data column
Dim bottomRow As Range
Dim bottomData As Range
Set col = ActiveSheet.Range("B7")
Set bottomRow = Range("A7").End(xlDown)
col.Select
If Not IsEmpty(Selection) Then 'If the worksheet is empty, nothing happens
Do
Set bottomData = Selection.End(xlDown)
If bottomRow.Row <= bottomData.Row Then
'Iterate through every column, select all the data in that column
'then call the create graph subroutine
Call CreateGraph
ActiveCell.Offset(0, 1).Select
Else
If IsEmpty(Selection.End(xlDown)) Then
Call CreateGraph
ActiveCell.Offset(0, 1).Select
Else
Range(Selection, Selection.End(xlDown)).Select
End If
End If
Loop Until IsEmpty(Selection)
End If
End Sub
Here's the CreateGraph subroutine as well. I'm happy the way that it works. I know it isn't the best way, but this is my first time using VBA.
Sub CreateGraph()
Dim startCell As Range 'Starting cell (important for column selection)
Dim graphRange As Range
Set startCell = Selection
Set graphRange = Range(startCell, startCell.End(xlDown)) 'Selects all data in column
'Create chart, define chart type and source data
ActiveSheet.Shapes.AddChart.Select
ActiveChart.ChartType = xlLine
ActiveChart.SetSourceData Source:=graphRange
'Change chart location so that all charts on a sheet are stacked in top left corner
With ActiveChart.Parent
.Top = Range("A1")
.Left = Range("A1")
End With
'Change chart title and other attributes
With ActiveChart
.HasTitle = True
.ChartTitle.Text = startCell.Offset(-2, 0).Value
End With
End Sub
I may still be misunderstanding what you want, but this should get you started.
Sub PlotDataById()
Dim dict As Object, id, ws As Worksheet, rngId As Range
Set ws = ActiveSheet 'or whatever
Set dict = IdRanges(ws.Range("B3")) 'get the ranges for each id
For Each id In dict
Set rngId = dict(id).Offset(0, 1) 'first set of data
Debug.Print "Plotting id - " & id & ":" & rngId.Address
Do While Application.CountA(rngId) > 0
'use resize() to pass only the occupied range
CreateGraph rngId.Resize(Application.CountA(rngId)), id
Set rngId = rngId.Offset(0, 1) 'next column over
Loop
Next id
End Sub
'Capture the ranges occupied by each id in a list, starting at `startCell`
' Assumes list is sorted by id
Function IdRanges(startCell As Range) As Object
Dim c As Range, id, currId, cStart As Range, dict As Object
Set dict = CreateObject("scripting.dictionary")
currId = Chr(0) 'some non-value
Set c = startCell
Do While Len(c.Value) > 0
id = c.Value
If id <> currId Then
If Not cStart Is Nothing Then
dict.Add currId, c.Parent.Range(cStart, c.Offset(-1, 0))
End If
Set cStart = c
currId = id
End If
Set c = c.Offset(1, 0)
Loop
dict.Add currId, c.Parent.Range(cStart, c.Offset(-1, 0))
Set IdRanges = dict
End Function
'Create a plot of `rngData` with title `chtTitle`
Sub CreateGraph(rngData As Range, chtTitle)
Dim co As Shape, cht As Chart, ws As Worksheet
Set ws = rngData.Parent
Set co = ws.Shapes.AddChart
With co.Chart
.ChartType = xlLine
.SetSourceData Source:=rngData
.HasTitle = True
.ChartTitle.Text = chtTitle
End With
With co 'all charts on a sheet are stacked in top left corner
.Top = ws.Range("A1").Top
.Left = ws.Range("A1").Left
End With
End Sub
Using Select/ActiveCell is not a very robust way to structure your code, and typically you can avoid almost all uses of that approach.

picture visible = true if cell contains data

I am trying to figure out simple code to make picture objects visible if particular cells contain data. Cells in range R12:R61 contains objects (pictures, ie. Round Rectangles) that are not visible (.visible = false).
If some cells in range P12:P61 contains data then corresponding hidden image in range R12:R61 of that row need to be visible. I've tried something like this:
Dim xPicRg As Range
Dim xPic As Picture
Dim xRg As Range
Set xRg = Range("R12:R61")
For Each xPic In ActiveSheet.Pictures
Set xPicRg = Range(xPic.TopLeftCell.Address & ":" & xPic.BottomRightCell.Address)
If Not Intersect(xRg, xPicRg) Is Nothing Then xPic.Visible = True
Next
I'm stuck with this one.
Let's imagine our input looking like this:
Then, working with Range("A1:B10"), the only picture that should be present is the one in rows 1 and 2, as for the other 3 there are numbers in column "A":
Sub TestMe()
Dim checkRange As Range
Dim myPic As Picture
With ActiveSheet
Set checkRange = .Range("A1:B10")
Dim myRow As Range
For Each myRow In checkRange.Rows
If WorksheetFunction.Count(myRow.Cells) > 0 Then
For Each myPic In .Pictures
Debug.Print myPic.TopLeftCell.Address
Debug.Print myPic.BottomRightCell.Address
Dim picRange As Range
Set picRange = .Range(.Cells(myPic.TopLeftCell.Row, myPic.TopLeftCell.Column), _
.Cells(myPic.BottomRightCell.Row, myPic.BottomRightCell.Column))
Debug.Print picRange.Address
If Not Intersect(picRange, myRow) Is Nothing Then
myPic.Visible = False
End If
Next
End If
Next
End With
End Sub

How to move a data set from one sheet to another based on criteria using VBA

I am trying to move data from one Table called Raw_Data on sheet Raw Data to another table called Phone_Number on sheet No Quality.
I have 16 columns on the tables and I need to confirm if the Raw Data table has the words No Quality or PH Phone on the 15th column. If it does then I want to move the data to the No Quality sheet and paste it into the table there. Once it is pasted I want to erase the data off of the Raw Data table.
I have tried a few different methods but can't seem to get them to work. Here is the first method I'm using
Sub Numbers()
Dim dataSheet As Worksheet, newSheet As Worksheet
Dim dataTable As ListObject, newTable As ListObject
Dim dataCount As Long
Dim checkOne As String, checkTwo As String
Dim copyRange As Range
Set dataSheet = Worksheets("Raw Data")
Set newSheet = Worksheets("No Quality")
Set dataTable = dataSheet.ListObjects("Raw_Data")
Set newTable = newSheet.ListObjects("Phone_Number")
checkOne = "PH Phone"
checkTwo = "No Quality"
dataCount = dataSheet.ListObjects("Raw_Data").ListRows.Count
dataValue = dataSheet.ListObjects("Raw_Data").DataBodyRange(dataCount, "O").Value
dataLocation = dataSheet.ListObjects("Raw_Data").DataBodyRange(dataCount, "O").row - 1
For i = 2 To dataLocation
valueToCheck = dataSheet.ListObjects("Raw_Data").DataBodyRange(i, "O")
If valueToCheck = checkOne Or valueToCheck = checkTwo Then
'Errors out on the line below
Worksheets("Raw Data").Range(Cells(i, "A"), Cells(i, "P")).Copy
Worksheets("No Quality").Cells(Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).row, 1).PasteSpecial
End If
Next i
End Sub
I can get it to partially run but it will never complete. I attempted to use the following code but I'm not sure how to change it to run in the way that I needed it.
Sub NoQuality()
Dim dataTable As Range
Dim newTable As Range
Application.ScreenUpdating = False
Set dataTable = Worksheets("Raw Data").ListObjects("Raw_Data").DataBodyRange
Set newTable = Worksheets("No Quality").ListObjects("Phone_Number").DataBodyRange
dataTable.Copy newTable.Offset(tbl2.Rows.Count)
Application.CutCopyMode = False
tbl1.ClearContents
Application.ScreenUpdating = True
End Sub
Results of New Code
You could probably get away with a lot less code. Please try the following & let me know how it goes.
Option Explicit
Sub Numbers()
Dim ws1 As Worksheet, ws2 As Worksheet
Set ws1 = Sheets("Raw Data")
Set ws2 = Sheets("No Quality")
With ws1.ListObjects("Raw_Data").Range
.AutoFilter 15, "No Quality", 2, "PH Phone"
.Offset(1).Resize(.Rows.Count - 1).Copy ws2.Cells(2, 1)
.Offset(1).Resize(.Rows.Count - 1).EntireRow.Delete
ws1.ListObjects("Raw_Data").AutoFilter.ShowAllData
End With
End Sub

Overwrite data being sent to table if it exists already?

I have a script that sends data from a userform to a table on a network drive. I also have code to populate that table data back in the form for users to make edits. Say i have an existing entry, pull the data to make updates, how would make sure it overwrites an already existing entry instead of appending extra rows? Could I implement an if statement to check if it exists already?
EDITED CODE:
Private Sub cmdSendData_Click()
Set wb = Workbooks.Open("\\\OFFER_LOG_DATA_TABLE.xlsx")
Dim wsTgt As Worksheet: Set wsTgt = wb.Worksheets("Sheet1")
Dim recRow As Range
'See if there's a match on an existing row
' adjust function to suit...
Set recRow = MatchRow(wsTgt.Range("A1").CurrentRegion, _
txtCandidateName.Text, _
txtCurrentPosition.Text)
'If there's no existing row to update then add a new row at the bottom
If recRow Is Nothing Then Set recRow = wsTgt.Range("A50000").End(xlUp).Offset(1, 0)
With recRow.EntireRow
.Cells(1).Value = txtTodays_Date.Text 'section 1
.Cells(2).Value = Me.cmbReason_for_Offer.Value
.Cells(33).Value = txtMgrJustification.Text
End With
wb.Close savechanges:=True
Application.Quit '????
wb.Saved = True
End Sub
'Return a row from a table based on matches in two columns
' returns nothing if no match
Function MatchRow(tableRange As Range, lStore, lName) As Range
Dim rw As Range
lStore = Me.txtStore.Text
lName = Me.txtCandidateName.Text
For Each rw In tableRange.Rows
'adjust the column numbers/match types as needed
If rw.Cells(4).Value = lStore Then
If rw.Cells(16).Value = lName Then
Set MatchRow = rw
Exit Function
End If
End If
Next rw
End Function
Should look something like this:
Private Sub cmdSendData_Click()
Set wb = Workbooks.Open("\\TABLE.xlsx")
Dim wsTgt As Worksheet: Set wsTgt = wb.Worksheets("Sheet1")
Dim recRow As Range
'See if there's a match on an existing row
' adjust function to suit...
Set recRow = MatchRow(wsTgt.Range("A1").CurrentRegion, _
txtCandidateName.Text, _
txtCurrentPosition.Text)
'If there's no existing row to update then add a new row at the bottom
If recRow is nothing then set recRow = wsTgt.Range("A50000").End(xlUp).Offset(1, 0)
With recRow.EntireRow
.cells(1).Value = txtTodays_Date.Text 'section 1
.cells(2).Value = Me.cmbReason_for_Offer.Value
'....
.cells(33).Value = txtMgrJustification.Text
End With
wb.Close savechanges:=True
Application.Quit '????
wb.Saved = True
End Sub
'Return a row from a table based on matches in two columns
' returns nothing if no match
Function MatchRow(tableRange As Range, match1, match2) As Range
Dim rw As Range
For Each rw In tableRange.Rows
'adjust the column numbers/match types as needed
If rw.Cells(1).Value = match1 Then
If rw.Cells(3).Value = match2 Then
Set MatchRow = rw
Exit Function
End If
End If
Next rw
End Function
Whatever code you have to load the existing record should keep track of which row it came from, or you will need some method to re-find the row when you save the record later.

How do i copy and paste data to worksheets that i created in VBA using for loop?

Im trying to copy and paste my data and assign them into different worksheets.For example, if column F is martin 1, the entire row that has martin1 will be paste to worksheets("Index1"). Same thing for Charlie 1 and it will be paste to worksheets("Index2"). However, I faced with a object defined error here as shown in my code below. Any ideas how to solve it?
Sub SaveRangewithConsecutiveDuplicateValuestoNewSheet()
'Define all variables
Dim wb As Workbook, ws As Worksheet, sCel As Range, rwNbr As Long
Set wb = ThisWorkbook 'Set workbook variable
Set ws = wb.Worksheets("Sheet1") 'set worksheet variable using workbook variable
Set sCel = ws.Cells(1, 6) 'Set the first start cell variable to test for duplicate values
Dim i As Integer
Dim site_i As Worksheet
For i = 1 To 3
Set site_i = Sheets.Add(after:=Sheets(Worksheets.count))
site_i.Name = "Index" & CStr(i)
Next i
Application.DisplayAlerts = False
For rwNbr = 2 To ws.Cells(ws.Rows.count, 6).End(xlUp).Offset(1).Row Step 1 'Loop
If ws.Cells(rwNbr, 6).Value = "Martin1" Then
ws.Range(sCel, ws.Cells(rwNbr, 6)).EntireRow.Copy Destination:=Sheets("Index1").Range("A1")
ElseIf ws.Cells(rwNbr, 6).Value = "Charlie1" Then
ws.Range(sCel, ws.Cells(rwNbr - ws.UsedRange.Rows.count, 6)).EntireRow.CopyDestination:=Sheets("Index2").Range("A1") '<----application defined or object defined error here
End If
Next rwNbr
Application.DisplayAlerts = True
End Sub
This is the link to my worksheet. https://www.dropbox.com/home?preview=Sample+-+Copy.xlsm
The final output should look something like this...
If your raw data does not have a header row then I would use a loop to gather up your target cells and copy them accordingly.
You will need to update your 3 target values inside Arr to Charlie1, Martin1, etc.
Macro Steps
Loop through each name in Arr
Loop through each row in Sheet1
Add target row to a Union (collection of cells)
Copy the Union to the target sheet where target Sheet Index # = Arr position + 1
Sub Filt()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1")
Dim cs As Worksheet
Dim Arr: Arr = Array("Value1", "Value2", "Value3")
Dim x As Long, Target As Long, i As Long
Dim CopyMe As Range
'Create 3 Sheets, move them to the end, rename
For x = 1 To 3
Set cs = ThisWorkbook.Sheets.Add(After:=Sheets(ThisWorkbook.Sheets.Count))
cs.Name = "Index" & x
Next x
lr = ws.Range("F" & ws.Rows.Count).End(xlUp).Row
'Loop through each name in array
For Target = LBound(Arr) To UBound(Arr)
'Loop through each row
For i = 1 To lr
'Create Union of target rows
If ws.Range("F" & i) = Arr(Target) Then
If Not CopyMe Is Nothing Then
Set CopyMe = Union(CopyMe, ws.Range("F" & i))
Else
Set CopyMe = ws.Range("F" & i)
End If
End If
Next i
'Copy the Union to Target Sheet
If Not CopyMe Is Nothing Then
CopyMe.EntireRow.Copy Destination:=ThisWorkbook.Sheets("Index" & Target + 1).Range("A1")
Set CopyMe = Nothing
End If
Next Target
End Sub
Tested and working as expected on my end, however....
If you had headers this would be much easier with a copy/paste. If you run the same macro on same book twice this will break for many reasons such as having duplicated sheet names, breaking the relationship between Sheet Index # = Arr Position + 1, etc...

Resources