I'm trying to synchronize the filters of 2 pivot table(just from one way, change filters in pivot2 when filters in pivot1 are changed but not the opossite).
But I get this error when I try getting the sheet.
st = Target.Worksheet
Excecution Time Erro 438
object doesnt support this property or method
The pivot in this worksheet might be changed in VBA Code of the event "WorkbookOpen" so it might not run when this worksheet is active, this is the code:
Private Sub Worksheet_PivotTableUpdate(ByVal Target As PivotTable)
If Target.Name <> "pivot1" Then Exit Sub
Dim st As Worksheet
Dim pivot1 As PivotTable
Dim pivot2 As PivotTable
Set st = Target.Worksheet
Set pivot2 = st.PivotTables("pivot2")
Set pivot1 = st.PivotTables("pivot1")
For Each pf In pivot1.PageFields
If pf.Name <> "Filter1" Then
pivot2.PageFields(pf.Name).CurrentPage = pf.CurrentPage
End If
Next
End Sub
Because a worksheet is an object, you need to use the Set keyword i.e. Set st = Target.Worksheet
On another note, are these PivotTables based on the same data source? If so, no VBA necessary: Just add a Slicer and connect them via right clicking the Slicer and selecting Report Connections.
I dimmed the variable:
Dim mainTableRange As Range
Then gave it a value:
Set mainTableRange = Range("tLedgerData") ' tLedgerData is an Excel table.
Now I'm trying to get the name of the table (which is "tLedgerData") from the variable to reference columns in that table even if the table name changes.
I tried
mainTableRange.Name
and
mainTableRange.Name.Name
(See how do you get a range to return its name.) Both threw run-time error '1004': Application defined or object-defined error.
mainTableRange.Select selected all table data excluding the header and total rows.
I think you're having an X-Y problem here: solving problem X when the solution is for problem Y.
[...] to reference columns in that table even if the table name changes
Have the table / ListObject alone on its own dedicated worksheet, and give the sheet a CodeName. That way you can do this:
Dim tbl As ListObject
Set tbl = LedgerDataSheet.ListObjects(1)
And now you have the almighty power of the ListObject API to do whatever it is that you want to do. For example, retrieve the column names:
Dim i As Long
For i = 1 To tbl.ListColumns.Count
Debug.Print tbl.ListColumns(i).Name
Next
In other words, you don't need to care for the name of the table. What you want is to work with its ListObject. And since you never need to refer to it by name, the table's name is utterly irrelevant and the user can change it on a whim, your code won't even notice.
I believe an Excel table and named-range are two different things which is why the .name.name doesn't work. A table is a ListObject and once you set a range equal to a table you should be able to continue to call that range without an error.
Curious, what is the reason why your table might change unexpectedly?
I wrote out some lines of code to show a couple things. You can create tables and reuse the range variables after the table name changes. You can also set AlternativeText for the table with some identifying string and use that to locate a particular table if you suspect the table name may change.
Option Explicit
Public TestTable As Range
Sub CreateTable()
ActiveSheet.ListObjects.Add(xlSrcRange, [$A$1:$C$4], , xlYes).name = "Table1"
ActiveSheet.ListObjects("Table1").AlternativeText = "Table1"
End Sub
Sub SetTableRange()
Set TestTable = Range("Table1")
End Sub
Sub SelectTable()
TestTable.Select
End Sub
Sub RenameTable()
ActiveSheet.ListObjects("Table1").name = "Table2"
[A1].Select
End Sub
Sub SelectRenamedTable()
TestTable.Select
End Sub
Sub ClearSelection()
[A1].Select
End Sub
Sub FindTable1()
Dim obje As ListObject
For Each obje In ActiveSheet.ListObjects
If obje.AlternativeText = "Table1" Then
MsgBox "Found " & obje.AlternativeText & ". Its current name is: " & obje.name
End If
Next obje
End Sub
Sub ConvertTablesToRanges()
' I found this snippet in a forum post on mrexcel.com by pgc01 and modified
Dim rList As Range
On Error Resume Next
With ActiveSheet.ListObjects("Table1")
Set rList = .Range
.Unlist ' convert the table back to a range
End With
With ActiveSheet.ListObjects("Table2")
Set rList = .Range
.Unlist ' convert the table back to a range
End With
On Error GoTo 0
With rList
.Interior.ColorIndex = xlColorIndexNone
.Font.ColorIndex = xlColorIndexAutomatic
.Borders.LineStyle = xlLineStyleNone
End With
End Sub
Thought provoking problem (for me at least). Normally when creating a chart you have your data and then use it to create the chart. If you then copy the chart to another workbook, the values on the chart stay the same but there is "no available" data source in the new workbook. I want to create a new chart which is the average of multiple copied charts. Is this possible in excel/vba?
I can't even try recording a macro and going from there as I don't know if its possible to "average" multiple charts.
EDIT : Been doing some more thinking and am thinking if it is possible to instead of extract data into a new sheet for each chart, is it possible to average data upon extraction. If on the chart you Right click -> select data, you can see the reference to the data in the original worksheet. Is it possible to average this and print just the outcome without having to store all the data? Would still be easier to directly average charts if possible!
EDIT 2: I have reworked my data template so that matching time series data ranges is no longer an issue. Also as per the comment on averages-of-averages, the data is all of equal weight and quantity so this should not be a problem. It literally just comes down to: is there a way to take the face values of multiple charts (or graphs), and average them to form a new chart (or graph) without massive data manipulation in the original (or new) workbook?
Bounty Summary (with round numbers): Looking for a quick'ish way in VBA to create a chart which is the average of multiple charts. I have 10 types of chart on 50 separate worksheets. I'm looking to create a summary sheet with 10 charts that average the data from the same respective chart on the other 50 sheets. The key difficulty is that this is a 'presentation Workbook that all charts are copied into, all the data for each chart is in a different workbook.
EDIT 4: Data is stored in multiple time series tables that are all side by side in a main data sheet. It appears to be at the moment (as per Scott's comment) that there is no way to directly manipulate and the most likely solution will be that of data extraction/manipulation. Search still continues though :)
I want to create a new chart which is the average of multiple copied charts. Is this possible in excel/vba?
It is possible but there is no magic formula for this task.
I would first iterate each workbook, each worksheet, each shape and aggregate the values in an array, with one array for each type of chart.
To avoid storing all the data, the averages will have to be computed upon each extraction like this:
Average = ((PreviousAverage * N) + Value) / (N + 1)
Next, to expose the data in your dashboard, I would duplicate the missing charts from the aggregated workbooks and reuse the one already present.
This way, the customisation of the dashboard will remain untouched if all the charts are already there.
Finally, I would directly insert the aggregated values in the charts without storing them in a sheet.
I've assemble a working example that aggregates all the charts from the current workbook and displays the results in the sheet "Dashboard":
Sub AgregateCharts()
Dim ws As Worksheet, wsDashboard As Worksheet, sh As Shape, ch As chart
Dim xValues(), yValues(), yAverages(), weight&, key
Dim items As Scripting.dictionary, item As Scripting.dictionary
Set items = CreateObject("Scripting.Dictionary")
' define the dashboard sheet
Set wsDashboard = ThisWorkbook.sheets("Dashboard")
' disable events
Application.ScreenUpdating = False
Application.EnableEvents = False
' iterate worksheets '
For Each ws In ThisWorkbook.Worksheets
' if not dashboard '
If Not ws Is wsDashboard Then
' iterate shapes '
For Each sh In ws.Shapes
If sh.type = msoChart Then ' if type is chart '
Debug.Print "Agregate " & ws.name & "!" & sh.name
' check if that type of chart was previously handled
If Not items.Exists(sh.chart.chartType) Then
' extract the values from the first serie
xValues = sh.chart.SeriesCollection(1).xValues
yValues = sh.chart.SeriesCollection(1).values
' duplicate the chart if it doesn't exists in the dashboard
Set ch = FindChart(wsDashboard, sh.chart.chartType)
If ch Is Nothing Then
Set ch = DuplicateChart(sh.chart, wsDashboard)
End If
' store the data in a new item '
Set item = New Scripting.dictionary
item.Add "Chart", ch
item.Add "Weight", 1 ' number of charts used to compute the averages
item.Add "XValues", xValues
item.Add "YAverages", yValues
items.Add ch.chartType, item ' add the item to the collection '
Else
' retreive the item for the type of chart '
Set item = items(sh.chart.chartType)
weight = item("Weight")
yAverages = item("YAverages")
' update the averages : ((previous * count) + value) / (count + 1) '
yValues = sh.chart.SeriesCollection(1).values
UpdateAverages yAverages, weight, yValues
' save the results '
item("YAverages") = yAverages
item("Weight") = weight + 1
End If
End If
Next
End If
Next
' Fill the data for each chart in the dashboard
For Each key In items
Set item = items(key)
Set ch = item("Chart")
' Add the computed averages to the chart
ch.SeriesCollection(1).xValues = "={" & Join(item("XValues"), ";") & "}"
ch.SeriesCollection(1).values = "={" & Join(item("YAverages"), ";") & "}"
Next
' restore events
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
Private Sub UpdateAverages(averages(), weight&, values())
Dim i&
For i = LBound(averages) To UBound(averages)
averages(i) = (averages(i) * weight + values(i)) / (weight + 1)
Next
End Sub
Private Function DuplicateChart(ByVal source As chart, target As Worksheet) As chart
' clone the chart to the target
source.Parent.Copy
target.Paste
Application.CutCopyMode = 0
' clear the data '
With target.Shapes(target.Shapes.count).chart.SeriesCollection(1)
Set DuplicateChart = .Parent.Parent
.name = CStr(.name)
.xValues = "={0}"
.values = "={0}"
End With
End Function
Private Function FindChart(source As Worksheet, chartType As XlChartType) As chart
' iterate each shape in the worksheet to fin the corresponding type
Dim sh As Shape
For Each sh In source.Shapes
If sh.type = msoChart Then
If sh.chart.chartType = chartType Then
Set FindChart = sh.chart
Exit Function
End If
End If
Next
End Function
Some data manipulation will probably be necessary. However, you can do it all in memory (or in a hidden worksheet if you prefer).
To extract data from a chart, example code:
Sub chartTest()
Dim ch As ChartObject
Set ch = Worksheets(1).ChartObjects(1)
Dim nr As Variant, var As Variant, var 2 As Variant
nr = UBound(ch.Chart.SeriesCollection(1).Values)
' Paste the values back onto the sheet
Range(Cells(1, 1), Cells(nr, 1)) = Application.Transpose(ch.Chart.SeriesCollection(1).XValues)
Range(Cells(1, 2), Cells(nr, 2)) = Application.Transpose(ch.Chart.SeriesCollection(1).Values)
' Pull the values into a variable (will be in array format)
var = ch.Chart.SeriesCollection(1).XValues
var2 = ch.Chart.SeriesCollection(1).Values
' Retrieval example
For i = 1 To UBound(var)
Range("A" & i).Value = var(i)
Range("B" & i).Value = var2(i)
Next i
End Sub
Whether you use Chart or ChartObjects as a first stop seems to depend on how the chart is created. The code in this example worked for a chart created by right-clicking some data in a sheet and inserting the chart.
See the Chart.SeriesCollection and the Series Properties pages on MSDN for more information.
So basically, extract all the data from the charts using code similar to the above, compare them, and create a new chart based on this data.
I have written several Subs to show/hide fields in a PivotTable.
Now I am trying to do the same with a calculated field, but I get an error when hiding it.
I took my code from the recorder and the recorder's code also halts on the last line.
I googled the error message, without serious result.
Sub PrRemove()
'remove PR
Dim pt As PivotTable
Set pt = ActiveSheet.PivotTables("MyPivot")
pt.PivotFields("MyField").Orientation = xlHidden '<- here is the error
End Sub
The same code works fine if MyField is a normal field (not a calculated one).
I am using Excel 2007 with SP2.
Any clue ?
EDIT on 17 June 2010: I also tried using pt.DataFields instead of pt.PivotFields, with exactly the same behaviour. The error message says "Unable to set the orientation of the PivotField class".
after much hair pulling i have found a workaround.
if you add more than one pivot field (calculated or otherwise) excel creates a grouped field called Values. you can set the orientation property of PivotField("Values") to xlHidden and it bullets both fields. So if you want to remove a calculated field, just add a non-calculated field, set PivotField("Values").orientation to xlHidden and you're done.
nobody said it would be pretty...
With ActiveSheet.PivotTables("PivottableName").PivotFields("Values")
.PivotItems("CalcFieldName").Visible = False
End With
I wanted to easily remove data fields (calculated fields or not), like it would be done manually.
And I finally found this solution (Excel 2010) :
Set pt = ActiveSheet.PivotTables("mypivottable")
For Each pi In pt.DataPivotField.PivotItems
pi.Visible = False
Next
Well, I will give you the confirmation you need. It seems using the Orientation property on a "Calulated Field" just does not work, and I would have to agree this is a bug and not a common "usage" error. I was able to duplicate "hiding/showing" the field without having to remove ("Delete") the calculated field. This allows the user to physically drag the calculated field from the field list after you have progammatically "hidden" the field. This is not a bad solution because it duplicates the user-interface. (Using Excel 2003.)
'2009.09.25 AMJ
'work around for
' 1004, Unable to set the Orientation property of the PivotField class
'when setting orientation property to hidden of calculated field, as in
' ActiveSheet.PivotTables("PivotTable1").PivotFields("Sum of Field1").Orientation = xlHidden
Public Sub Hide()
'hide the data without removing the calculated field
' this allows the user to physically drag the
' calculated field from the field list once we
' have "hidden" it programmatically.
' if we use the "delete" method, the field is removed
' from the pivot table and the field list
Dim oWS As Worksheet
Dim oPT As PivotTable
Dim oPF As PivotField
Dim oPI As PivotItem
Set oWS = ActiveSheet
Set oPT = oWS.PivotTables(1)
For Each oPF In oPT.DataFields
If oPF.SourceName = "Field1" Then
'Stop
Exit For
End If
Next
Set oPI = oPF.DataRange.Cells(1, 1).PivotItem
'oPI.DataRange.Select
oPI.Visible = False
End Sub
Public Sub Show()
'show just reads the pivot field to the data fields
Dim oWS As Worksheet
Dim oPT As PivotTable
Dim oPF As PivotField
Set oWS = ActiveSheet
Set oPT = oWS.PivotTables(1)
For Each oPF In oPT.PivotFields
If oPF.SourceName = "Field1" Then
'Stop
Exit For
End If
Next
oPT.AddDataField oPF
End Sub
[original answer]
Most likely you cannot hide this item because it is the last visible item. Instead, try removing it.
Here is a little workaround I discovered today, again not very elegant but at least it doesn't need much code, it will hide ALL the fields and you will need to reshow the ones you want after:
objTable.DataPivotField.Orientation = xlHidden
You may run into an error if excel for some reason thinks the datapivotfield is empty, but to fix this just add in another field as a datafield right before the above statement. Also make sure its the letter l not the number 1 in xlHidden vba's default font has them looking very very similar.
Happy Coding
It seems that to hide a calculated field you need to first hide a pivot field called "Values".
PivotTable(1).PivotFields("Values").Orientation = xlHidden
For Each PF In PT.DataFields
PF.Orientation = xlHidden
Next PF
I'm assuming that field only seem to exist if you've got two or more fields in your xlDataField position.
Thanks Alinboss for pointing me in the right direction. I was sure I tried your method before and failed - turns out the order is important!
P.s. Your code still does not work with only one calculated data field
Laurent Bosc's code checks out so I voted it up. My full code includes adding data after hiding it all. The code is placed on Sheet1(Sheet1).
Private Sub Refresh_Pivot()
Dim NewMetric As String
Dim pt As PivotTable, objDataField As Object
NewMetric = "your_custom_metric"
'-------update pivot table 1, hide all elements including calculated field----
Application.EnableEvents = False
Set pt = Sheet1.PivotTables("PivotTable1")
For Each Pi In pt.DataPivotField.PivotItems
Pi.Visible = False
Next
'--------add a new data field to the pivot table----------------------------
With pt
.AddDataField.PivotFields(NewMetric), "Sum of " & NewMetric, xlSum
End With
Application.EnableEvents = True
End Sub
I don't think this is an excel bug, I think it's a 'feature'. ;-)
Re: #AMissico, there is no problem in excel hiding all of the fields in a pivot table, but he may be talking about items - you can't hide the last item in a pivot field.
This is the code I routinely use to do what you are trying to do. These macros were developed on Excel 2002 & 2003. I don't hide CalculatedFields, I delete them.
' Hide all fields.
' #param ThePivotTable to operate upon.
Sub HidePivotFields(ByVal ThePivotTable As PivotTable)
Dim pField As PivotField
For Each pField In ThePivotTable.CalculatedFields
pField.Delete
Next pField
For Each pField In ThePivotTable.PivotFields
pField.Orientation = xlHidden
Next pField
Set pField = Nothing
End Sub
' Removes FieldName data from ThePivotTable
Sub HideField(ByVal ThePivotTable As PivotTable, _
ByVal FieldName As String)
If FieldExists(ThePivotTable, FieldName) = True And _
CalculatedFieldExists(ThePivotTable, FieldName) = False Then
ThePivotTable.PivotFields(FieldName).Orientation = xlHidden
End If
End Sub
' Returns True if FieldName exists in ThePivotTable
'
' #param ThePivotTable to operate upon.
' #param FieldName the name of the specific pivot field.
Function FieldExists(ByVal ThePivotTable As PivotTable, _
ByVal FieldName As String) As Boolean
Dim pField As PivotField
For Each pField In ThePivotTable.PivotFields
If pField.SourceName = FieldName Then
FieldExists = True
Exit For
End If
Next pField
Set pField = Nothing
End Function
' Checks if the field FieldName is currently a member of the
' CalculatedFields Collection in ThePivotTable.
' #return True if a CalculatedField has a SourceName matching the FieldName
' #return False otherwise
Function CalculatedFieldExists(ByVal ThePivotTable As PivotTable, _
ByVal FieldName As String) As Boolean
Dim pField As PivotField
CalculatedFieldExists = False
For Each pField In ThePivotTable.CalculatedFields
If pField.SourceName = FieldName Then
CalculatedFieldExists = True
End If
Next pField
Set pField = Nothing
End Function
' Returns a Pivot Field reference by searching through the source names.
'
' This function is a guard against the user having changed a field name on me.
' #param ThePivotTable to operate upon.
' #param FieldName the name of the specific pivot field.
Function GetField(ByVal ThePivotTable As PivotTable, _
ByVal FieldName As String) As PivotField
Dim pField As PivotField
For Each pField In ThePivotTable.PivotFields
If pField.Name <> "Data" Then
If pField.SourceName = FieldName Then
Set GetField = pField
Exit For
End If
End If
Next pField
Set pField = Nothing
End Function
' Counts the number of currently visible pivot items in a field.
' #param ThePivotItems the collection of pivot itemns in a field.
' #return the count of the visible items.
Function PivotItemCount(ByVal ThePivotItems As PivotItems) As Long
Dim pItem As PivotItem
Dim c As Long
For Each pItem In ThePivotItems
If pItem.Visible = True Then c = c + 1
Next pItem
PivotItemCount = c
Set pItem = Nothing
End Function
' Hides a single pivot item in a pivot field, unless it's the last one.
' #param FieldName pivot field containing the pivot item.
' #param ItemName pivot item to hide.
Sub HidePivotItem(ByVal ThePivotTable As PivotTable, _
ByVal FieldName As String, _
ByVal ItemName As String)
Dim pField As PivotField
Set pField = GetField(ThePivotTable, FieldName)
If Not pField Is Nothing Then
If PivotItemCount(pField.PivotItems) > 1 Then
pField.PivotItems(ItemName).Visible = False
End If
End If
Set pField = Nothing
End Sub
I am having the exact same problem as you.
It looks like I'm going to have to delete the calculated field and readd it rather than hiding/showing it.
I accidentally discovered a workaround to this the first time I attempted to hide a calculated field, so thought I would share it here:
Instead of modifying the orientation property, you can instead instruct the code to select the cell in the pivot table that contains the title of the calculated field you want to hide, and then delete the selection. This works as long as you have another datafield already in the table. Example below:
Scenario: Pivot table covers the range A1:G10. Calculated field "Margin" is already in the table, and you want to add the data field "Sales" and remove the "Margin" calc field.
Code to execute:
'Add Sales data field
ActiveSheet.PivotTables(Pname).AddDataField ActiveSheet.PivotTables( _
Pname).PivotFields("SALES"), "Sum of SALES", xlSum
'At this point, the datafield titles are in vertically adjacent rows, named "Sum
'of Margin" and "Sum of Sales" at locations B3 and B4 respectively.
'Remove the "Sum of Margin" calculated field
Range("B3").Delete
Not sure why this works, but I'm glad we at least have this to work with!
Fortunately there is a very easy way to hide a datafield. You were all wrong mistakeing pivotfields with datafields. I'm presenting a piece of code that empties a pivot table no matter how many pivot fields/data fields were initially in the pivot :
Sub Macro1()
Dim p As PivotTable
Dim f As PivotField
Set p = ActiveSheet.PivotTables(1)
For Each f In p.PivotFields
If f.Orientation <> xlHidden Then
f.Orientation = xlHidden
End If
Next f
For Each f In p.DataFields
If f.Orientation <> xlHidden Then
f.Orientation = xlHidden
End If
Next f
End Sub
Have you changed the name of the calculated field? Was it originally 'Sum of MyField'? Try looking at the SourceName property and if it's different using that.
Have you tried pt.CalculatedFields("MyField").Orientation = xlHidden ?
I know it is kind of late, but i see that this problem has not been answered confidently yet and i was facing the same problem having hard time to find useful info. So, i hope this post may help somebody...
If you have your data stored in data model, then instead of PivotFields, use CubeFields .
I had the same problem and i experimented with a simple workbook which did not had a data model and my code worked perfectly (using PivotFields).
It only returned error in the workbook with the data model.
So, i made this change and boom! it worked!
My suggestion is to use the following code:
Sub PrRemove()
'remove PR
Dim pt As PivotTable
Set pt = ActiveSheet.PivotTables("MyPivot")
pt.CubeFields("MyField").Orientation = xlHidden '<- here is the error
End Sub
Thanks to #user4709164 answer i got this code, its working perfectly for me:
my pivot columns all ends with X or Y to indicate axis so i use the last char for field caption.
Public Sub PivotFieldsChange()
Dim ValType As String, param As String
Dim pf As PivotField
Dim pt As PivotTable
Application.ScreenUpdating = False
Sheet = "mysheet"
'select between % calculated column or normal column
If Range("Z1").Value = 1 Then
ValType = "%"
Else: ValType = ""
End If
Application.EnableEvents = False
For Each pt In Sheets(Sheet).PivotTables
Select Case pt.Name
Case "case1": param = "param1"
Case "case2": param = "param2"
Case "case3": param = "param3"
Case Else: GoTo line1
End Select
pt.PivotFields("Values").PivotItems("X").Visible = False
pt.PivotFields("Values").PivotItems("Y").Visible = False
pt.PivotFields (param & ValType & "_X")
pt.PivotFields(param & ValType & "_X").Orientation = xlDataField
pt.PivotFields (param & ValType & "_Y")
pt.PivotFields(param & ValType & "_Y").Orientation = xlDataField
For Each pf In pt.DataFields
pf.Function = xlAverage
pf.Caption = Right(pf.Caption, 1)
Next
line1:
Next pt
Application.EnableEvents = True
End Sub
Here is some sample VBA code:
Sub Macro1()
Dim pt As PivotTable
Set pt = ActiveSheet.PivotTables("SomePivotTable")
'Set colOfFields = pt.PivotFields
End Sub
The third line is incomplete/broken. What is the correct way to get access to collection of all the fields in a PivotTable? I need to be able to loop over them. Actual coding is being done in C# VSTO Project.
This works for me (Excel 2003 [11.8146.8202] SP2):
Sub Macro1()
Dim pt As PivotTable
Dim col As PivotFields
Dim c As PivotField
' Name of the pivot table comes from right clicking on the pivot table,
' Table Options..., Name field.
Set pt = ActiveSheet.PivotTables("PivotTable1")
Set col = pt.PivotFields
For Each c In col
Debug.Print c.Name
Next
End Sub
Ok. Found some C#-flavored code ideas from:
http://blogs.msdn.com/andreww/archive/2008/07/25/creating-a-pivottable-programmatically.aspx
// pvtTable is an Excel.PivotTable set earlier in the code
Excel.PivotFields pflds =
(Excel.PivotFields)pvtTable.PivotFields(System.Type.Missing);
foreach (Excel.PivotField pf in pflds)
{
//some code here
}
The trick is passing in the System.Type.Missing to get the "collection" of fields back.