I have a spreadsheet that links to several other spreadsheets to pull data that compiled into one sheet; I am tasked with modifying the sheet to graph this data. There is a row of 12 slots for each month in a person's report; if a person was working in a month then the month will be displayed in the appropriate slot. For the graph I have figured out to use a dynamic name range so that the number of months in the graph is equal the number of months present in the line; the only problem is that the start pointing changes based on what month a person started.
I am able to make a rather large nested if statement but that doesn't seem like a very good way to approach resolving my problem. Is there a way I can have a VBA script change the value of the dynamic name range or just modify the chart's value directly? If this isn't a viable option is there any other way to approach this problem other than a dynamic named range?
I can't change how this report sheet behaves as this is a spreadsheet related to work and that's how they want it to be for reporting reasons; I'm simply looking to add the graphing functionality.
EXAMPLES:
https://www.dropbox.com/s/zn6yt4l6kjvwq33/Example1.xlsx
https://www.dropbox.com/s/j88tgoik68s4lhx/Example2.xlsx
In the first example you see an example of the problem when you select "Second" from the dropbox next to Agent Name. It's including pointless data in the graph.
In the 2nd example I have resolved the problem by adding an if statement to the dynamic named ranges used in the chart; but as you can see to include 2 months it's now massive and to include all 12 it would be pretty much impossible. If I were to add a 3rd person who started in March they would also not work in the 2nd example.
Is there a way I can make it so the values in the Dynamic name range are changeable through VBA? Can I modify the chart's values directly using VBA? Is there a way to bypass this problem without even using VBA?
Thanks.
(I'm not 100% certain I've understood the problem, but this might at least help to get closer...)
Try defining three names, something like this:
start_month
data
chart_data
Where start_month contains the number of the column that you want to be charted, data is all twelve columns of data, something like =OFFSET($C$1,0,0,COUNT($A:$A),12) and chart_data selects the column you want from the data, e.g. =INDEX(data, , start_month)
Now set your chart's series to WorkbookName!chart_data, using whatever your workbook's called. As the start_month value changes, so should the values displayed in the chart.
You can add or modify a Name in VBA like:
Dim nm As Name
Dim rng As Range
'Here, you could use a more complicated expression'
' to derive the proper address dynamically.'
Set rng = Sheets("Sheet1").Range("A2:A25")
'Now, add a Name to the workbook:'
Set nm = ActiveWorkbook.Names.Add("My_Name", rng)
With some string functions/variables, you could derive/caclulate the range address in VBA.
Related
So I'm trying to create a Forecast using historic data from 2 years. Each year's data is broken down into weeks and weeks that haven't occurred yet are set to 0. I'm struggling trying to create a formula that will automatically run a Forecast on only the weeks in the year that have occurred. I created this formula which Excel won't execute:
=UNIQUE(FILTER(WkSht!G:(VLOOKUP(F1,DH2:DI54,2)),(WkSht!A:A)=(B1)))
I'm trying to use VLOOKUP to replace the second part of a cell reference based off a lookup table. So if F1 is 25, for example, then the Filter function will be:
=UNIQUE(FILTER(WkSht!G:AE,(WkSht!A:A)=(B1))
That second formula works on its own as intended, but I'm trying to create this excel file so that it requires minimal work to update in the future and manually changing the range seems like a bit too much work to expect other people to do.
So I guess my question is:
How do I change part of the reference automatically?
Maybe I could do:
=UNIQUE(FILTER(VLOOKUP(F1,DH2:DI54,2)),(WkSht!A:A)=(B1)))
And have to lookup values contain the reference text?
Alternatively, is there a way to filter out the last of the 0's in the FORECAST.ETS function (as some values might intentionally be 0s in earlier weeks)?
To get a variable width range, you can use the construct
SomeRange:INDEX(MaxRange,,NumberOfColumns)
In your case SomeRange would be
WkSht!G:G
MaxRange would be something like
WkSht!G:Z
where you replace Z with a column that comfortably covers all your (future) data
NumberOfColumns is your VLookup (You probably want an exact match?, If so include the 4th parameter =0)
Demo:
=UNIQUE(FILTER(WkSht!G:G:INDEX(WkSht!G:Z,,VLOOKUP(F1,DH2:DI54,2,0)),WkSht!A:A=B1))
Sample data on sheet WkSht
Many "advanced" (aka: VBA) excel tutorials on the web or even excel's vba help encurage us to use the
Range("B2:B10")
method (to be precise: object) for selecting cells or getting values. In the same place they often add it's totally ok to use predefined names as well:
Range("valuesabove")
On the other hand I fell in love with the incredible power of relatively defined cell names. They make it so much easier to write and handle big composite formulas, and basically to refer to nearly anything.
However, relative names don't work in the Range("valuesabove") method the way we are used to it.
Usually (when used on the worksheet) relative names are relative to the currently selected cell or to the cell in which they are used.
In VBA's Range() object this is not true. Range is relative to a WorkSheet object, by default to the ActiveSheet. But ActiveSheet is represenetd by its leftupper cell, A1. And this is what Range turns out to be relative to. And this is why absolute names ($C$23) do work with it, and relative ones ("one column to the left, two rows up") don't.
So my question is:
How can I harness the power of relative names in VBA then?
EDIT:
Realising that my question was rather unclear (thx's go to you guys commenting tirelessly) let me try to put it in a specific form and clarify terms:
IMHO on an excel worksheet it is very comfortable to use names in order to refer to cells or define calculated values by functions based on cell values.
In excel a reference to a cell can be either relative, absolute, or mixed. This is true also when creating names. Thus we can speak about absolute, relative or mixed names (in terms of referring of course).
Here an absolute name is used a couple times (created using excel's Trace Dependents function):
Name "name" = $D$2
A relative name is used a couple times here:
Name "upright24" while, e.g. cell A7 is selected = C3 (without $ signs!). But this changes constantly according to the selected cell or region. You can check it in the name manager! (Ctrl+F3)
And this is what we can consider as a mixed name:
Name "rel_serialnumber" while, e.g. cell C6 is selected = $B6. The row of which (6) changes constantly according to the selected cell or region.
The creation of a relative or a mixed name is explicitly based on the active cell at the moment of creating the name. The creation of an absolute name naturally doesn't rely on the cursor position.
Note, that
absolute names mean a dinamic offset from the referenced cell, which is one and only
relative names mean a static offset from the referenced cell, which thus changes always corresponding to the place where the name is used
mixed names mean a mixed (or half-dynamic) offset from the referenced cell, the row or column of which thus changes always corresponding to the place where the name is used while the other remains always the same (the offset in one or the other direction remains zero).
Okay, now here is the thing. I have a database-like excel sheet where I handle the rows like records and the columns as fields for properties. The user uses this thing as follows: he "selects a record" by placing the cursor in any cell of the row of the desired record. Then he presses a big command button which starts my VBA macro. This intends to open a prepared skeleton file and fill some specific cells in it (which are btw defined by absolute names) with some values (which are defined by mixed names) from the selected record.
Since Range("name") is considered ok to use in VBA (see above) I thought Range("relativename") or Range("mixedname") will work just as fine while automatically relying on the active cell.
I couldn't be worse.
Only Range("absolutename") works in the way one would expect! Explanation see above.
So I'm after a function / method / object that is possibly as comfortable to use with a "relativename" or a "mixedname" as Range("absolutename") is.
It appears you are looking for Range.Offset() http://msdn.microsoft.com/en-us/library/office/ff840060%28v=office.15%29.aspx
However you could do it as:
'Your example Range(Col_B_in_current_row) as
Range("B" & ActiveCell.Row).Select
'Your example Range("B2:B10") -> Range("valuesabove") as
Range("B2:B10").Offset(-1, 0).Select
Just seems like a relatively simple syntax already exists for this.
I think I've found a proper and compact solution. It's
Names("mixedname").RefersToRange
Not as short as Range("mixedname") would be but it is really providing the expected values.
UPDATE:
This solution is mostly unuseful if you want to copy relative-named cell values in a source workbook to relative-named cells in a dest workbook with a single codeline. This is because Names() relies on the actual position of the cursor which is depending on which workbook is currently the active one and in most cases this won't be ok for the other.
In this case the non-fixed part of the name has to be stored:
sourcerow = ActiveCell.Row
[...]
'opening a wbk, this also makes it the active one
[...]
Names("dest").RefersToRange = mysheet.Cells(sourcerow, mybook.Names("src").RefersToRange.Column)
To reference a Range relative to another Range you can use this syntax:
myRange.Range("namedRange")
Note: This only works if both the Row offset AND the Column offsets are positive. For example if the "Refers to" formula for the named range is "=Offset(A1,r,c)", then the above syntax will throw an error if Either r Or c is negative. But, it will work if both are positive.
The asymmetry is unfortunate but business as usual for VBA...
To Reference the third column in the row of the current ActiveCell:
ActiveCell.EntireRow.Range("C1")
To reference a cell offset by (for example) 1 row and 3 columns relative to the ActiveCell:
ActiveCell.Range("C2")
Obviously, you can use the same syntax with the Selection Object or any other Range value in VBA.
Private Sub Worksheet_Change(ByVal Target as Range)
If Not Intersect(Target.Address,ThisWorkbook.Sheets('sheetname).Range('RangeName)) Is Nothing Then _
'Do whatever you want down here.
ThisWorbook.Sheets('sheetname).Range('RangeName).Offset(0,Target.Row)
End If
End Sub
This should send you on the right path to what you want (which is super unclear). Use the worksheet change event to bring in user worksheet selections and changes into VBA modules. Put it into the relevant sheet.
I had the same problem, but I did get it to work - sort of. I don't know what is different about the simple example below, but it works. At first I thought selection mattered, but no - it works without changing the active cell.
(I still can't get it to work in my main spreadsheet.)
Named range: "TestName" = Sheet1!$H1
Values in H1:H10 = 1,2,3,4,5,6,7,8,9,10
Sub Test()
Dim x As Integer
For x = 0 To 10
Range("A1").Offset(x, 0).Value = Range("A1").Offset(x, 0).Range("Testname").Value
Next x
End Sub
Result: A1:A10 = 1,2,3,4,5,6,7,8,9,10
I have one column that contains my x data and two columns that contain my y data. I would like to plot y against x, but the length of each column is dependent on a counter variable, i. I do not have experience with vba, but do with coding. Can someone outline the correct syntax to perform this task?
Please and Thank you!
You could goto your 'Developer' tab, and record a macro. While in record mode, create the graph using the data and specifications you desire.
End recording of the macro, and you will then have a template of VBA code (ALT+F11) to work with as a foundation.
It sounds like the best solution is to simply make the X-Y scatter plot ranges longer than you ever expect the data to be (Excel will ignore the blank rows).
But if you want to get fancy, you can populate your scatter plot data with dynamic named ranges that automatically adjust to the length of your data without bothering with VBA:
Create a dynamic named range (AKA named formula) representing your X data column.
Example (assuming column A, and data starting at A1):
=$A$1:INDEX($A$1:$A$1000,MATCH(TRUE,ISBLANK(Sheet1!$A$1:$A$1000),0)-1)
Change $A$1 so that it corresponds to the first row of your data. Change $A$1000 to a row number that is longer than you ever expect your data to be.
Call the named formula above "XColumn" or something similar. To make a named range, do Formulas->Define Name.
Set the X range of your scatter plot equal to:
=Sheet1!XColumn
Note that Excel's plotting window will give an error (devoid of any helpful information regarding how to fix the problem!) if you try to input the named range without the sheet name, even if the named range is scoped to the entire workbook, and even if it's on the same worksheet! -- Annoying, right?
Do this for each column and scatter plot range (Y1 = Y1Column, Y2 = Y2Column, etc.).
Note that this will not work correctly if your columnar data contains text, blanks, errors, etc., but the method can be modified to handle these issues.
To test and make sure your dynamic named ranges are being created as expected, in any cell you can enter:
=SUMPRODUCT(XColumn)
Then do Formulas-->Evaluate Formula-->Evaluate to make sure the XColumn array contains the data you want it to contain.
Optional tip: create another named range called FirstColumn set to the location of the first column, e.g. $A:$A. Make another named range called FirstRow and set it to the location of the first row, e.g. $1:$1. Make yet another named range called MaxRow and set it to the maximum length you ever expect your data to be, e.g. $1000:$1000. Finally:
Replace all instances of $A$1 above with:
INDEX(FirstRow,0,COLUMN(FirstColumn)+<DATA TABLE COLUMN NUMBER - 1>)
Replace all instances of $A$1000 with:
INDEX(MaxRow,0,COLUMN(FirstColumn)+<DATA TABLE COLUMN NUMBER - 1>)
<DATA TABLE COLUMN NUMBER - 1> will be different for each named formula (e.g. 0 for XColumn, 1 for Y1Column, 2 for Y2Column, etc etc).
Now if you ever want to create new data tables and scatter plots in different locations, instead of doing all of the work over again you can just copy and paste your named ranges and you only have to change one or two things instead of 15! Additionally, if the data ever gets longer than you expected, you only have to change one thing instead of 3.
Excel's built-in Table functionality is a great way to automatically increase a range-size when new data is added. Simply select your data, then either press Ctrl + t or go to Insert > Table. Then when you reference your new Table from the Chart it will increase as your data does.
I am looking for help with a macro so I can easily sort and see data.
I have a spreadsheet with rows 1 and 2 containing item information. Rows 3-93 have the ordering information. For every row in 3-93, if there is a value in any column in between F and VU, copy the the corresponding value in columns A,B,C for that row to a new sheet called Orders and copy the corresponding values in rows 1 and 2 for that column.
for example, I need this:
......A..........B...........C...................................F...........................................GU
1..................................................100235410 (Navy Shirt)......101028 316 (Moss FR Plaid)
2..................................................................XL.........................................2XL T
3..Jack....Brown...Current.............................2..............................................1
Copied over to a new sheet called Orders, like this:
.......A.........B.............C.......................................D................................E..................F
1...Jack...Brown....Current..............100235410 (Navy Shirt).................XL.................2
2...Jack...Brown....Current............101028 316 (Moss FR Plaid).........2XL T..............1
I am struggling with the VBA logic and can't seem to get it work right. Any help or advise is greatly appreciated.
Thanks!
A problem like this can be a challenge to get your head around if you just dive into the coding especially for beginners it requires the definition of bite sized chunks that can be coded as descrete processes. I suggest you create a flow chart or step-by-step procedure that describes what you would do if you had to make the changes by hand. If your procedure accurately describes those steps, you can then create code that does each of the individual steps and string those code snips together in the order you described in your flow chart.
Also, be sure to define processes that can be repeated for each row or sheet or other descrete data set you will work with. You can then loop these processes to avoid writing the same code over and over again.
For your example above, if I understand correctly, you will first want to define the range in which customers are defined: in this case the range from A3 to A93 (or whatever the highest row number might be. If that varies, you'll want to define a range that changes with the particular data set (using the .End method, for example, if there are no empty cells in the data (ideally there should not be) you could do this:
Dim wbOrders As Workbook
Set wbOrders = ActiveWorkbook
Dim wsOrders As Worksheet
Set wsOrders = wbOrders.ActiveSheet
Dim rngOrders As Range
Set rngOrders = wsOrders.Range("A3", wsOrders.Range("A3").End(xlDown))
Once you have defined the range in which orders are found, you can loop through the data to work with the individual orders:
Dim rngCustomer As Range
For Each rngCustomer In rngOrders
'~~>Here you could define other ranges to work with (perhaps columns F to VU of the
' current row)
'~~>Here you could also define other loops/routines to move the data
Next rngCustomer
Inside the above loop structure you could Dim a range of customer orders, loop through the range finding columns that contain data, use each column with found data (one at a time) to get the data(probably one cell at a time) from that row and rows 1 and 2, transfering it either onto either a new
sheet, OR into an array of values that can be added to all new sheets after the first loop finds and defines all the customer order data and puts it in the array.
I hope this gives you enough to start tackling the problem. If you hit snags during the coding of an individual step, search around on StackOverflow or the web in general to find the solutions. If you don't find them, post a question that details what you've tried and includes the specific non-working code to StackOverflow and I almost guarantee you'll have an answer in 24 hours or less.
I'm trying to create a chart with a range built dynamically using the INDIRECT function. Excel does recognize the range I am creating using INDIRECT as it highlights the corresponding range on the sheet:
However when saving the chart, I get an error message saying the function is not valid:
Does anybody know what the problem is / how to create a dynamic chart range from a specific start to specific end point?
PS: You can download the above spreadsheet here. The formula I was using:
=INDIRECT("sheet!"&E2&":"&E3)
The way you are trying to do it is not possible. Chart data range has to have a fixed address.
There is a way around this, and that's using named ranges
Put the number of rows you want in your data in a cell (e.g., E1)
So, using your example, I put Number of Rows in D1 and 6 in E1
In name manager, define the names for your data and titles
I used xrange and yrange, and defined them as:
xrange: =OFFSET(Sheet1!$A$2,0,0,Sheet1!$E$1)
yrange: =OFFSET(Sheet1!$B$2,0,0,Sheet1!$E$1)
now, to your chart - you need to know the name of the workbook (once you have it set up, Excel's function of tracking changes will make sure the reference remains correct, regardless of any rename)
Leave the Chart data range blank
for the Legend Entries (Series), enter the title as usual, and then the name you defined for the data (note that the workbook name is required for using named ranges)
for the Horizontal (Category) Axis Labels, enter the name you defined for the labels
now, by changing the number in E1, you will see the chart change:
Mine is similar to Sean's excellent answer, but allows a start and end day. First create two named ranges that use Index/Match formulas to pick the begin and end days based on E2 and E3:
rngDay
=INDEX(Sheet1!$A:$A,MATCH(Sheet1!$E$2,Sheet1!$A:$A,0)):INDEX(Sheet1!$A:$A,MATCH(Sheet1!$E$3,Sheet1!$A:$A,0))
rngValue
=INDEX(Sheet1!$B:$B,MATCH(Sheet1!$E$2,Sheet1!$A:$A,0)):INDEX(Sheet1!$B:$B,MATCH(Sheet1!$E$3,Sheet1!$A:$A,0))
You can then click the series in the chart and modify the formula to:
=SERIES(Sheet1!$B$1,Sheet1!rngDay,Sheet1!rngValue,1)
Here's a nice Chandoo post on how to use dynamic ranges in charts.
Just another answer for bits and googles..
If you still want to refer to your start and end cells, you'll need to add a separate formula for your Day Range and your Values Range. Formulas are below and the screenshot shows the formulas used.
Day Range:
="Sheet1!"&$F$2&":"&ADDRESS(ROW(INDIRECT($F$3)),COLUMN(INDIRECT($F$2)))
Values Range:
="Sheet1!"&ADDRESS(ROW(INDIRECT($F$2)),COLUMN(INDIRECT($F$3)))&":"&$F$3
Then add two ranges referencing the INDIRECT values of those cells
Press Ctrl+F3, Click New, and add a new range with the name "chart_days", referring to =INDIRECT(Sheet1!$F$4); and a new range with the name "chart_values", referring to =INDIRECT(Sheet1!$F$5)
Finally, in your chart, add a series that refers to =nameOfYourWorkbook!chart_values
and Edit the category to refer to =nameOfYourWorkbook!chart_days
I use OFFSET to create a defined name formula so that I may define all the ranges for the data, allowing me to have a starting a ending date (or the beginning and ending position of any data set).
For a simple graph, I define the name CategoryLabels as follows:
= OFFSET($A$5; (InicitialMonth-1); 0; LastMonth - (InitialMonth-1))
and DataCars as follows:
= OFFSET($B$5; (InicitialMonth-1); 0; LastMonth - (InitialMonth-1))
You will have to define as many names as Series you want to include, following the same procedure. In this simple case, I only included Car sales.
Initial Month and Last Month are Range Names defined for single cells used to indicate which months of the graph will be included (from starting to ending months).
Remember, as explained by Sean Cheshire and others, that to use the names for the chart values, the name of the spreadsheet must be included.
According to the formula you have shown: =INDIRECT("sheet!"&E2&":"&E3)
you are not naming the sheet correctly.
I would have thought it would be Sheet1! or Sheet2! etc.
Your formula resolves to =sheet!E2:E3 which is not a valid address. The error message you are getting means Excel cannot resolve the input to INDIRECT. INDIRECT is a valid function so the argument you offer it must be invalid.
All of the above answers which state the sheet name have corrected your error but do not mention it... ;)
Named formula with Indirect functions DOES NOT WORK IN CHARTS. It works in other froms as your desired dynamic source will be highlighted, but when you it in chart, it would not be evaluated. Hope Microsoft put a fix on this.
When a line chart's range is a named variable, and the variable has INDIRECT() references through a cell to a range, then the variable must have at least 2 INDIRECT()s separated by a comma.