Starting with screenshot:
http://i.imgur.com/Isj9MER.png
(I'm a new user, can't post images)
Working for a call center. We have a program that tracks our time spent in various phone states (so when we're on calls, out to lunch, etc) that can export data for a given team and date range as a CSV.
I'm working on automating this report. The way it works is that the team lead will pull the CSV, copy-paste into another tab, and then I've got a bunch of array formula If functions and Indirect references to pull all the data as shown. The data analysis and everything is working great.
My problem is the graph. Right now, I've got column B with an If function that either outputs the agent's email (which is how the system tracks it) or "" if all emails have been used. The rest of the columns have If(B2="","", [relevant formula]). That way, we can have all the team leads with various (and fluctuating) team sizes use the same report with a simple copy-paste.
My problem is the stupid bar chart. It pulls data from rows 2-32 (A2:A32). Our current largest team is 28, and I left room for new hires showing up soon. My problem happens when I use data from one of our smaller teams. As you can see, even though the blank rows are filled with "" in every cell, it's still displaying those rows. In the chart. This means that with the smallest team (shown), the chart is half wasted whitespace.
Is there a way to make the column chart only show rows that have actual data in them?
One thing I tried was putting an Indirect reference for Series Values. So I had a cell (AA1) with {=MAX(IF(B2:B31="","",ROW(B2:B31)))}. That outputs the row number of the last non-blank row. Then for the Series Values I put =Indirect("Report!A2:A"&AA1), but Excel gave me an error saying the function was not valid. I guess you can only have an actual range (and not a formula) in the data input for a chart.
Excel 2016, by the way.
I come up with three possible solutions for this problem.
Convert your data table to a pivot table and use a pivot chart (currently only available in Windows version, sorry if you are using a Mac).
Use a bit of VBA to hide the empty rows.
Use a bit of VBA to modify the data displayed on the chart.
Sample Data Setup
Although you provided a screen shot of your data, it is not simple to convert that into a test case to demonstrate the three solutions. Therefore, I threw together this very simple set up.
Column A contains a list of possible "users".
Cell D1 is a user entry to change the contents of column B and C.
Column B contains the actual "users". It is calculated with =IF(A2<=$D$1,A2,"")
Column C contains the data that goes with the "users". It is calculated with =IF(B2<>"",10,"")
A chart is added to the sheet.
Below is a screenshot of the sample setup, where all potential users are included. (Note: It is Sheet1)
Below is a screenshot of the sample setup, where only potential users A through E are included.
The white space in the second image is the problem we are trying to address.
SOLUTION 1: Make a pivot table of the data
Select all of the pertinent data, in this case B1:C12.
Select Insert -> Pivot Table
In the Create Pivot Table dialog, make sure "New Worksheet" is selected, and click OK.
On the Pivot Table, place "User" field in Rows, and "Total" field in Values. Select the Value Field Settings... for the "Total" field and make sure it uses Sum.
In the Pivot Table, select the Row Labels drop down, Label Filters, Greater Than... . Type "" into the dialog and select OK.
From Pivot Table Tools -> Analyze, select PivotChart. Choose Bar Chart from the dialog.
Below is a screen shot of the Pivot Table.
On the tab with the data, change the last potential user from E to G. On the Pivot Table, refresh the data.
Below is a screen shot of the refreshed pivot table.
SOLUTION 2: Use VBA to hide empty rows
The below code is attached to a button made visible on the worksheet. (n.b. There are other ways to activate the code such as change events, but this will depend on a number of factors outside the scope of this answer).
Sub HideRows()
Dim AllCatRange As Range
Set AllCatRange = Worksheets("Sheet1").Range("B2:B12")
Dim iLoop As Long
For iLoop = 1 To AllCatRange.Rows.Count
If AllCatRange.Cells(iLoop, 1) = "" Then
AllCatRange.Cells(iLoop, 1).EntireRow.Hidden = True
Else
AllCatRange.Cells(iLoop, 1).EntireRow.Hidden = False
End If
Next iLoop
Set AllCatRange = Nothing
End Sub
Below is a screen shot of the data tab with the button added.
After clicking the button, it now looks like this ...
This code will expand rows when they contain data, and collapse rows when they do not.
A potential problem with this approach is that expanding/collapsing rows will change the size of the chart, if the chart lays over those rows.
SOLUTION 3: Use VBA to modify the chart
The below code is attached to a button, and used to modify the chart after the source data is changed.
Sub ModifyChart()
Dim AllCatRange As Range
Set AllCatRange = Worksheets("Sheet1").Range("B2:B12")
Dim lastRow As Long
lastRow = 1
Dim iLoop As Long
For iLoop = 1 To 11
If AllCatRange.Cells(iLoop, 1) <> "" Then
lastRow = lastRow + 1
End If
Next iLoop
Dim PlotCatRange As Range
Set PlotCatRange = Worksheets("Sheet1").Range("B2:B" & lastRow)
Dim PlotSerRange As Range
Set PlotSerRange = Worksheets("Sheet1").Range("C2:C" & lastRow)
Worksheets("Sheet1").ChartObjects(1).Chart.FullSeriesCollection(1).XValues = "=Sheet1!" & PlotCatRange.Address
Worksheets("Sheet1").ChartObjects(1).Chart.FullSeriesCollection(1).Values = "=Sheet1!" & PlotSerRange.Address
Set AllCatRange = Nothing
Set PlotCatRange = Nothing
Set PlotSerRange = Nothing
End Sub
Below is a screenshot of the data tab with the new button in place.
Below is a screenshot of the tab after clicking the button.
Wrap up
My personal preference is to use VBA to modify the chart after the data is modified, as this reflects what you would do manually.
I found a solution! Thanks largely to this page. Here's what I did:
I created made a named range using this formula: =OFFSET(Report!$B$1,1,0,COUNTIF(Report!$B$2:$B$30,"<>-"),1)
For that to work, I had to change all the empty cells to output "-" when empty in stead of "". I couldn't get COUNTIF to accept "<>""" or "<>" or any other weird tricks I tried. Using "-" was easier.
That makes a dynamic named range that changes size as I put data into the sheet. I named a similar range for everything I'm trying to chart (Approved Status, Call Ready, Not Ready). The chart was able to accept those names and now it's dynamically sized. If I only have three agents on the sheet, it shows three huge bars. With twenty, it shows twenty bars (exactly what I was looking for).
One other tip: I changed my first row to output "" when empty, so that COUNTIF(Report!$B$2:$B$30,"<>-") always returns at least 1 (otherwise you get annoying errors because you have a named range referencing a 0-length array).
Related
First - I am NOT a programmer, so please be patient and know I may not use the correct terminology. :)
I am 95% done with a project using VBA in Excel Developer (first time ever!) to create a userform to populate a spreadsheet with organizational risks. At the bottom of the userform, I have a list that displays the risks already entered into the spreadsheet for reference, but I want to exclude "closed" risks from list view on the userform (but I want to retain them in the source spreadsheet).
Column J (column 10) contains the status, so I need something like Column J <> "Closed" (wildcards because there are different types of "closed") in the if/then statement, but I don't have the right syntax or maybe I need to define Column J so the filter knows where to look?? A friend suggested I may need to filter in a completely separate sub - but not sure how to accomplish that. Excerpt below.
'define number of columns (fields) in the database to display at the bottom of screen and identify column headers
.lstRiskDatabase.ColumnCount = 11
.lstRiskDatabase.ColumnHeads = True
'Assign column widths - note summary column is intentionally 0
.lstRiskDatabase.ColumnWidths = "35,55,75,175,150,160,0,55,55,100,100"
'assign control source for rows on the database table
If iRow > 1 Then
.lstRiskDatabase.RowSource = "RiskDatabase!A2:K" & iRow
Else
.lstRiskDatabase.RowSource = "RiskDatabase!A2:K2"
End If
So I've created a graph to appear on this separate sheet titled "Report", and above it you can see that there are two dropdown boxes in which a user can select a specific date range. I would essentially like to link this user-specified date range to the range displayed on the graph. I'm having trouble figuring out how to link the source data for the chart to the date selectors.
The data for the chart is sourced from this separate sheet titled "Database", in which the data is chronologically ordered with corresponding Feed values. The goal is to match the dates selected by the user in the "Report" section to the dates in this "Database" sheet, and after finding the corresponding initial and start date, apply that same logic to the feed data to strictly display the feed data within that date range.
I am currently sourcing the chart data using this Macro, the xData and yData are linked to lists titled "Dates" and "FeedTonnage", which are basically tables I made out of the columns in the database. I'm not sure if the tables are even needed, but the database will continue to be updated overtime as more data will be coming in so I thought a table would've been useful for this.
So I've been trying to write a sub that sifts through the Date column to look for a date that matches the dates selected in the "Report" sheet. I'm doing this because I intend on using the cell address to get the corresponding data from the Feed column by replacing the column portion of the cell address with that of the Feed, and yeah, I'm kinda lost at this point so I could really use some input.
Instead of changing the data range of the chart, you can hide the rows of data outside the selected dates:
Worksheets("Database").Rows("3:100").Hidden = False
For i = 3 To 100
If Worksheets("Database").Range("A" & i).Value < Worksheets("Report").Range("B1").Value Or Worksheets("Database").Range("A" & i).Value > Worksheets("Report").Range("C1").Value Then
Worksheets("Database").Rows(i).Hidden = True
End If
Next i
The code above assumes the dates are in the range A3:A100, the start date is in B1 and the end date is in C1. Adjust the ranges as needed
looking through the forum and can't find what I need. I have 100+ sheets with unique sheet names and data in column B. Column B will contain various END DATES. some sheets will have 1 or 2 end dates, others have upwards of 30 end dates. . I would like to create a summary page containing a table that will update itself to show all sheet names that have END dates in column B expiring within the next 30 days. is this something that requires coding? or use of the excel indirect formula with maybe a vlookup wrapped around it?
Private Sub ChkEndDates()
Dim ws As Worksheet
Dim cell As Range
Dim rowCount as Long
For Each ws in Worksheets
If ws.Name <> '"summary page" Then
rowCount = WorksheetFunction.CountA(ws.Range("B:B"))
For i = 1 To rowCount
If (ws.Cells(i, 2).Value - Date < 30) And (ws.Cells(i, 2).Value > Date) Then
MsgBox ws.Name
'And maybe push into the summary page
End If
Next i
End If
Next ws
End Sub
Neither VLOOKUP nor INDIRECT are required for this problem.
Assuming your dates in the B columns are in Excel's serial number format, this formula will calculate the amount of cells in the B column of Sheet2 that are within 30 days from today's date:
= SUMPRODUCT((Sheet2!B:B>=TODAY())+0,(Sheet2!B:B<=(TODAY()+30))+0)
You can just duplicate this formula for however many sheets you need, e.g.
= SUMPRODUCT((Sheet2!B:B...))+SUMPRODUCT((Sheet3!B:B...))
EDIT
Per #Jeeped's comment, it is recommended to narrow down this range (B:B) just to the end of your data. (e.g. if you have at most 30 rows of data, change to B1:B30)
For completeness, here's how you would do this using PowerQuery/Get & Transform if you had Excel 2013 or later.
First, you would turn each of the data areas in your workbook into Excel Tables (aka ListObjects), by selecting them and either using the Insert>Table command from the ribbon, or simply by using the keyboard shortcut [Ctrl] + [T]. Ideally you would give them all a name that denotes what they are. (I've used the prefix "Input_" and then a running count, because later this 'handle' will help me to ignore any Tables that I don't want in my end result, simply by seeing if it's name is prefixed with "Input_").
Then from the Data tab, select New Query>From Other Sources>Blank Query:
A mysterious looking UI called the PowerQuery window will open with nothing much of interest in it. If you type =Excel.CurrentWorkbook into the formula bar and press Enter, then a list of the various Tables in your workbook will populate in that window, as shown below. And if you click that twin arrow icon to the right of the Content column, a menu will appear that lets you expand those tables to include the actual columns in each of them, and bring them into the PowerQuery window:
And here's how that data dump looks, when you push OK:
This next bit is only required if you have other Tables in your workbook already that you don't want to show in your end mashup. In the screenshot below, I'm filtering the result set to only include tables with the Input_ prefix:
And then I'm going to change the data type of that DateTime column to a simple Date, by selecting the column concerned, and then choosing Data Type>Date from the Transform tab.
Then I'm going to select Close and Load To from the menu at top right:
..and then select the "Only Create Connection" and "Add to Data Model" options from the resulting dialog:
Now back in Excel, I create a PivotTable, and leave the "Use this workbook's Data Model" option checked:
...which brings up a slightly different looking PivotTable Fields List than you usually see:
I now add the Name (Table Name) and End Date fields to my Pivot, and set a date filter to just show dates within the next 30 days:
And here's the end result, a PivotTable that shows just those tables with end dates within the next 30 days:
The beauty of this approach is that if you later add more tabs with more input tables, then provided you prefix them with "Input_" then they will automatically appear next time you refresh the PivotTable. And furthermore, instead of having a report that merely tells you which tabs have applicable End Dates, the PivotTable also tells you which individual records in those tabs are involved.
There's more stuff I would do along the way that I haven't shown, such as give the columns friendlier names by ditching the "Content." suffix, and I'd probably write a simple macro to automatically refilter the PivotTable based on 30 days from the current date. But this at least shows you the benefits of upgrading to Excel 2013 and using PowerPivot/Get and Transform to radically automate tasks such as this.
I have created a pivot table that lists the number of visitors to a store on a given date. There are 3 columns of data, the date column and two columns of numbers representing the visitors. When I create a chart from this data I get a bar of data that represents dates before and after the range of dates that make up the data. I can suppress all dates that occur prior to the dates I want to view by using the command:
With Worksheets("Pivot Tables").PivotTables("Weekly Statistics").PivotFields("Date")
.PivotItems.Item(1).Visible = False
However, I can't find a way to suppress the dates that occur after today's date. This leaves me with a column on my bar chart that is blank and has the axis value ">3/27/2013". I can suppress it by actually typing this line:
.PivotItems.Item(">3/27/2013").Visible = False
but having to manually do this every time I update the sheet is laborious and makes the sheet unusable to anyone else.
I tried to create a variable that would update the value inside the () but I can't get it to work.
Dim t
t = Worksheets("Data").Range("i3").Value
.PivotItems.Item(t).Visible = False
(where i3 is a cell in the Worksheet("Data") that is a concatenation of the date; in this case the cell contents are ">3/27/2013" )
Thanks
You could try to use alternative filtering technique:
t="3/27/2013"
Worksheets("Pivot Tables").PivotTables("Weekly Statistics").PivotFields("Date").PivotFilters.Add Type:=xlBefore, Value1:=t
However, it is used as of Excel 2007.
If not working try to start with:
Worksheets("Pivot Tables").PivotTables("Weekly Statistics").PivotFields("Date").ClearAllFilters
I've got a spreadsheet with plenty of graphs in it and one sheet with loads of data feeding those graphs.
I've plotted the data on each graph using
=Sheet1!$C5:$C$3000
This basically just plots the values in C5 to C3000 on a graph.
Regularly though I just want to look at a subset of the data i.e. I might just want to look at the first 1000 rows for example. Currently to do this I have to modify the formula in each of my graphs which takes time.
Would you know a way to simplify this? Ideally if I could just have a cell on single sheet that it reads in the row number from and plots all the graphs from C5 to C 'row number' would be best.
Any help would be much appreciated.
OK, I had to do a little more research, here's how to make it work,
completely within the spreadsheet (without VBA):
Using A1 as the end of your desired range,
and the chart being on the same sheet as the data:
Name the first cell of the data (C5) as a named range, say TESTRANGE.
Created a named range MYDATA as the following formula:
=OFFSET(TESTRANGE, 0, 0, Sheet1!$A$1, 1)
Now, go to the SERIES tab of the chart SOURCE DATA dialog,
and change your VALUES statement to:
=Sheet1!MYDATA
Now everytime you change the A1 cell value, it'll change the chart.
Thanks to Robert Mearns for catching the flaws in my previous answer.
This can be achieved in two steps:
Create a dynamic named range
Add some VBA code to update the charts data source to the named range
Create a dynamic named Range
Enter the number of rows in your data range into a cell on your data sheet.
Create a named range on your data sheet (Insert - Name - Define) called MyRange that has a formula similar this:
=OFFSET(Sheet1!$A$1,0,0,Sheet1!$D$1,3)
Update the formula to match your layout
Sheet1!$A$1 set this to the top left hand side of your data range
Sheet1!$D$1 set this to the cell containing the number of rows
3 set this value to the number of columns
Test that the named range is working:
Select the dropdown menus Edit - Go To, type MyRange into the reference field.
Your data area for the chart should be selected.
Add some VBA code
Open the VBA IDE (Alt-F11)
Select Sheet1 in the VBAProject window and insert this code
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address <> "$D$1" Then Exit Sub
'Change $D$1 to the cell where you have entered the number of rows
'When the sheet changes, code checks to see if the cell $D$1 has changed
ThisWorkbook.Sheets("Sheet1").ChartObjects(1).Chart.SetSourceData _
Source:=ThisWorkbook.Sheets("Sheet1").Range("MyRange")
' ThisWorkbook.Sheets("Chart1").SetSourceData _
Source:=ThisWorkbook.Sheets("Sheet1").Range("MyRange")
'The first line of code assumes that chart is embedded into Sheet1
'The second line assumes that the chart is in its own chart sheet
'Uncomment and change as required
'Add more code here to update all the other charts
End Sub
Things to watch for
Do not directly use the named range as the data source for the chart. If you enter the named range "MyRange" as the Source Data - Data Range for the chart, Excel will automatically convert the named range into an actual range. Any future changes to your named range will therefore not update your chart.
Performance might be impacted by the approaches listed above.
The OFFSET function in the named range is "volatile" which means that it recalculates whenever any cell in the workbook calculates. If performance is an issue, replace it with the INDEX formula.
=Sheet1!$A$1:INDEX(Sheet1!$1:$65536,Sheet1!$D$1,2)
The code fires everytime data is changed on Sheet1. If performance is an issue, change the code to run only when requested (i.e. via a button or menu).
You could look at dynamic ranges. If you use the OFFSET function, you can specify a starting cell and the number of rows and columns to select. This site has some useful information about assigning a name to an OFFSET range.
You can set the range for a chart dynamically in Excel. You can use something like the following VBA code to do it:
Private Sub Worksheet_Change(ByVal Target as Range)
Select Case Target
Case Cells(14, 2)
Sheet1.ChartObjects(1).Chart.SetSourceData Range("$C5:$C$" & Cells(14,2))
...
End Select
End Sub
In this case, the cell containing the number of the last row to include is B14 (remember row first when referring to the Cells object). You could also use a variable instead of the Cells reference if you wanted to do this entirely in code. (This works in both 2007 and 2003.) You can assign this procedure to a button and click it to refresh your chart once you update the cell containing the last row.
However, this may not be precisely what you want to do ... I am not aware of a way to use a formula directly within a chart to specify source data.
Edit: And as PConroy points out in a comment, you could put this code in the Change event for that worksheet, so that neither a button nor a key combination is necessary to run the code. You can also add code so that it updates each chart only when the matching cell is edited.
I've updated the example above to reflect this.
+1s for the name solution.
Note that names don't really really reference ranges, they reference formulae. That's why you can set a name to something like "=OFFSET(...)" or "=COUNT(...)". You can create named constants, just make the name reference something like "=42".
Named formulae and array formulae are the two worksheet techniques that I find myself applying to not-quite-power-user worksheets over and over again.
An easy way to do this is to just hide the rows/columns you don't want included - when you go to the graph it automatically excludes the hidden rows/columns
Enhancing the answer of #Robert Mearns, here's how to use dynamic cells ranges for graphs using only the Excel's formulas (no VBA required):
Create a dynamic named Range
Say you have 3 columns like:
A5 | Time | Data1 | Data2 |
A6 | 00:00 | 123123 | 234234 |
...
A3000 | 16:54 | 678678 | 987987 |
Now, the range of your data may change according to the data you may have, like you have 20 rows of data, 3000 rows of data or even 25000 rows of data. You want to have a graph that will be updated automatically without the need to re-set the range of your data every time you update the data itself.
Here's how to do it simply:
Define another cell that it's value will have the number of the occupied cells with data, and put the formula =COUNTIF(A:A,"<>"&"") in it. For example, this will be in cell D1.
Go to "Formulas" tab -> "Define Name" to define a name range.
In the "New Name" window:
i. Give your data range a name, like DataRange for example.
ii. In the "Refers to" set the formula to: =OFFSET(Sheet1!$A$1, 0, 0,Sheet1!$D$1,3),
where:
Sheet1!$A$1 => Reference: is the Reference from which you want to base the offset.
0 => Rows: is the number of rows, up or down, that you want the upper-left cell of the results to refer to.
0 => Columns: is the number of columns, to the left or right, that you want the upper-left cell of the results to refer to.
Sheet1!$D$1 => Height: is the height, in number of rows, that you want the result to be.
3 => Width: is the width, in number of columns, that you want the result to be.
Add a Graph, and in the "Select Data Source" window, in the Chart data range, insert the formula as you created. For the example: =Sheet1!DataRange
The Cons: If you directly use the named range as the data source for the chart, Excel will automatically convert the named range into an actual range. Any future changes to your named range will therefore not update your chart.
For that you need to edit the chart and re-set the range to =Sheet1!DataRange every time. This may not be so usable, but it's better than editing the range manually...