How to create cascaded (or dependent) page filters in a Pivot Table

mikecroom

New Member
Joined
Oct 8, 2010
Messages
47
Intro
We have some big data sets to summarise using pivots. And Excel page-filter drop-downs are not as helpful as our users would like. I want to see if I can give them what they want in VBA without insisting on 2010 Slicer functionality.<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:eek:ffice:eek:ffice" /><o:p></o:p>
<o:p></o:p>
Our problem<o:p></o:p>
There is a definite "hierarchy" of dependent filters for our data - is best outlined in terms of what the analysts at Amazon must have: eg:<o:p></o:p>
Page Filter 1: Dept (Department)<o:p></o:p>
Page Filter 2: Item<o:p></o:p>
<o:p></o:p>
And you might have Geography for a row field, Months for Column and Sum of sales as data<o:p></o:p>
<o:p></o:p>
The problem is that if you filter on a particular dept the dropdown filter for Item still offers every item from every department. What we want is to just offer the items from that department (ie possible matches given the dept filter)<o:p></o:p>
I need to pick up filter change events and then make some filter values invisible.<o:p></o:p>
<o:p></o:p>
Where I am so far:<o:p></o:p>
I can pick up a change in the pivot using Worksheet_PivotTableUpdate<o:p></o:p>
I can tell which filter (if any) changed<o:p></o:p>
I can define a hierarchy of filters (hard-coded)<o:p></o:p>
But how can I tell which items to display in the Page Field (for Item) if I change the Dept Page field?<o:p></o:p>
<o:p></o:p>
Thoughts so far: Either:<o:p></o:p>
Duplicate the pivot table. Remove the Row fields and set Item as a Row field. I should then have possible values of Item. Capture them and use to set visible values in the original pivot<o:p></o:p>
OR<o:p></o:p>
Use the property DataBodyRange. Find the column (Dept) and filter on the selected value. Capture the possible values for item (capturing unique)<o:p></o:p>
OR<o:p></o:p>
Is there a way of querying the internal data of a pivot to get what I want?<o:p></o:p>
OR<o:p></o:p>
Has anyone managed to do this already? Am on 2003 but could maybe get upgrade to 2007 for users.<o:p></o:p>
<o:p></o:p>
I have seen this question posted many times and not seen an answer - but some indication that 2010 Slicers are the answer. Would be interested in confirmation.<o:p></o:p>

<o:p></o:p>
Mike
 

Excel Facts

How to find 2nd largest value in a column?
MAX finds the largest value. =LARGE(A:A,2) will find the second largest. =SMALL(A:A,3) will find the third smallest
Andrew

Thank you. I can make it work - but it doesn't quite fit our bill in terms of the users:
They will want to use the pivot page filters "as is" without a "strange" set of boxes next door.

I am still working on a solution based on the page filters themselves

Mike
 
Upvote 0
I managed to answer my own question here - code submitted if you find it helpful. <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:eek:ffice:eek:ffice" /><o:p></o:p>

<HR style="COLOR: white" align=center SIZE=1 width="100%" noShade>

See initial post in this thread for Intro <o:p></o:p>
<o:p></o:p>
Where I am so far:<o:p></o:p>
I think the code will work on Excel 2003 - need to test some more. May need to recreate the pivot in 2003 format<o:p></o:p>
<o:p></o:p>
Problem is that in 2007, setting the Visible Property of an Item just decides whether it has a tick in it (in the dialog box). What I really want to do is hide the irrelevant items.<o:p></o:p>
<o:p></o:p>
I can't work out how to attach a file on this board (despite some help elsewhere in this forum) so here is the code to attach to any sheet containing a pivot. You need at least 2 page fields.


I'll post something more refined when I have it - and with less debug lines
Last problem I found is that you can't set CurrentPage to "(All)" unless all items are visble. Excel silently ignores you!

Mike <o:p></o:p>

Code:
'
' This code enables the cascaded filtering of page items in a pivot table.
' The cascade is determined by the order of the page items on the worksheet
'
Option Explicit
Dim SavedFilterValues(100) As Variant
Dim SavedNFields As Integer
Dim SavedFilterRows(100) As Integer
Dim PageFields(100) As String
Dim ChangedPageField As Integer
Dim NPageFields As Integer
Dim lastcol As Integer
Dim FirstTime As Integer
'
' capture 1st entry to the worksheet with the pivot and save the values of filters
' Then we can tell which filters changed
' may make more sense to move to wokbook open - but would need to hardwire which sheet/pivot we are talking about'
'
Private Sub Worksheet_Activate()
Dim FieldList(100) As String
Dim FieldType(100) As Variant
Dim NFields As Integer
Dim p As PivotTable
Dim WhatChanged As Variant
    Debug.Print ActiveSheet.Name
 
    If (FirstTime = 0) Then
        Debug.Print "1st time"
        If (ActiveSheet.PivotTables.Count <> 1) Then
            MsgBox ("More than 1 pivot on worksheet")
        Else
            Set p = ActiveSheet.PivotTables(1)
            SavedNFields = BuildFieldTable(p, FieldList, FieldType, SavedFilterValues, WhatChanged)
        End If
    Else
        Debug.Print "NOT 1st time"
    End If
 
    FirstTime = 1
 
 
End Sub
'
' Build table based on fields in pivot
'
' FieldList():  Names
' FieldTypes(): Orientation. eg whether Page or Row etc
' SavedFilterRows():    For Page Filters - row where found on sheet
' SavedFilterValues():    For Page Filters - last value of Page Filter (CurrentPage)
Private Function BuildFieldTable(Pivot As PivotTable, FieldList() As String, FieldType() As Variant, FilterValues() As Variant, WhatChanged As Variant) As Integer
Dim i, FieldIndex As Integer
Dim field As PivotField
Dim FieldOrientation As Variant
 
Dim NFields As Integer
NPageFields = 0
WhatChanged = ""
On Error GoTo 0
    FieldIndex = 1
    For i = 1 To Pivot.PivotFields.Count
        Set field = Pivot.PivotFields(i)
        FieldOrientation = field.Orientation
        'Debug.Print "Field ", field.Name, " ", field.Orientation
        Select Case FieldOrientation
            Case xlHidden, xlDataField
                Debug.Print "Skip ", FieldList(i)
            Case xlColumnField, xlPageField, xlRowField
                    'Debug.Print "Field ", field.Name, " ", field.Orientation
                    Debug.Print field.Name, " ", field.DataRange.Address
 
                    FieldList(FieldIndex) = field.Name
                    FieldType(FieldIndex) = field.Orientation ' row, page etc
                    If (FieldOrientation = xlPageField) Then
                        NPageFields = NPageFields + 1
                        SavedFilterRows(FieldIndex) = field.DataRange.Row
                        lastcol = field.DataRange.Column
 
                        If (field.CurrentPage <> SavedFilterValues(FieldIndex)) Then
                            WhatChanged = field.Name
                            Debug.Print "Page Field ", WhatChanged, " changed. Value is now ", field.CurrentPage
                        End If
                        SavedFilterValues(FieldIndex) = field.CurrentPage
                    End If
 
                    FieldIndex = FieldIndex + 1
            Case Else
                Debug.Print "ERROR"
        End Select
        'Debug.Print "Filter Value is", field.Value
    Next i
 
    NFields = FieldIndex - 1
 
 
    BuildFieldTable = NFields
 
    If (WhatChanged = "") Then
 
        If (NPageFields <> SavedNFields) Then
            WhatChanged = "xNF"
        Else
            WhatChanged = "Refresh"
        End If
    End If
End Function
Private Function PageFieldOrder(WS As Worksheet, FieldType() As Variant, FieldList() As String, WhatChanged As Variant) As Integer
    Dim StartRow, StartCol, i As Integer
    Dim ChangedPageField As Integer
 
' work out page order precedence
' work out the order of the page fields
    StartRow = 1
    StartCol = lastcol + 1
 
    WS.Cells(StartRow, StartCol).Value = "Field"
    WS.Cells(StartRow, StartCol + 1).Value = "Row"
    StartRow = StartRow + 1
 
    For i = 1 To SavedNFields
        If (FieldType(i) = xlPageField) Then
            WS.Cells(StartRow, StartCol).Value = FieldList(i)
            WS.Cells(StartRow, StartCol + 1).Value = SavedFilterRows(i)
            StartRow = StartRow + 1
        End If
    Next i
 
    Debug.Print ActiveSheet.Name, StartCol, StartRow
    Debug.Print 1, StartCol, "<>", StartRow - 1, StartCol + 1
 
    'Debug.Print WS.Name
    'WS.Activate
     Debug.Print WS.Range(WS.Cells(1, 3), WS.Cells(3, 4)).Address
 
    WS.Range(WS.Cells(1, StartCol), WS.Cells(StartRow - 1, StartCol + 1)).Select
    Selection.Sort Key1:="Row", Order1:=xlAscending, Header:=xlYes, _
        OrderCustom:=1, MatchCase:=False, Orientation:=xlTopToBottom, _
        DataOption1:=xlSortNormal
 
    ChangedPageField = -1
    For i = 1 To NPageFields
        PageFields(i) = WS.Cells(i + 1, lastcol + 1).Value
        Debug.Print PageFields(i)
        If (PageFields(i) = WhatChanged) Then ChangedPageField = i
    Next i
PageFieldOrder = ChangedPageField
End Function
'
' Respond to pivot table update
' Possible causes:
'   Filter Change
'   Data Refresh
'   Reformat
Private Sub Worksheet_PivotTableUpdate(ByVal OriginalPivot As PivotTable)
Dim OriginalWorkSheetName, TempWorkSheetName As String
Dim TempWorkSheet As Object
Dim FieldIndex
Dim FieldList(100) As String
Dim fieldname As String
Dim WhatChanged As Variant
Dim FieldType(100) As Variant
Dim field As PivotField
Dim TmpPivot As PivotTable
Dim FieldOrientation As Variant
Dim Thisfield As PivotField
 
Dim i, Nrows As Integer
Dim KeepRowName As String
Dim ErrorFlag As Integer
Dim WhichField As Integer
Dim j As Integer
Dim PF As String
 
Dim ChangedPf As PivotField
Dim item As PivotItem
Dim temp As Variant
Dim ScratchWorkSheet As Object
Dim StartRippleFieldName As String
Dim StartRipple As Integer
Dim ExcelVersion As String
Dim SUoff As Boolean
SUoff = False
SUoff = True
 
ErrorFlag = 1
On Error GoTo clearup
Application.EnableEvents = False
Application.DisplayAlerts = False
If (SUoff) Then Application.ScreenUpdating = False
 
OriginalWorkSheetName = ActiveSheet.Name
Debug.Print OriginalPivot.Name
 
If (ActiveSheet.PivotTables.Count <> 1) Then
    Debug.Print "More than 1 pivot on worksheet. No processing"
    GoTo clearup ' sorry about the goto, but need to clearup before exit
End If
SavedNFields = BuildFieldTable(OriginalPivot, FieldList, FieldType, SavedFilterValues, WhatChanged)
Select Case OriginalPivot.Version
    Case 1, 2 ' xlPivotTableVersion11
        ExcelVersion = "2003"
    Case 3 'xlPivotTableVersion12
        ExcelVersion = "2007"
    Case 4 ' xlPivotTableVersion14
        ExcelVersion = "2010"
    Case Else
        ExcelVersion = "Unknown"
End Select
 
If (NPageFields < 2) Then
    Debug.Print "Cascaded filter needs more than 1 page to be activated"
    GoTo clearup
End If
Set ScratchWorkSheet = Sheets.Add
ChangedPageField = PageFieldOrder(ScratchWorkSheet, FieldType, FieldList, WhatChanged)
 
' without these 2 lines you get stale pivot items and error 1004
OriginalPivot.PivotCache.MissingItemsLimit = xlMissingItemsNone
OriginalPivot.PivotCache.Refresh
If (ChangedPageField <> -1) Then
    ' enable all if set to "All"
    Set ChangedPf = OriginalPivot.PivotFields(PageFields(ChangedPageField))
    Debug.Print ChangedPf.CurrentPage
    If (StrComp(ChangedPf.CurrentPage, "(All)") = 0) Then
        For Each item In ChangedPf.PivotItems
            Debug.Print "All clearing item", item.Value
            item.Visible = True
        Next item
    End If
End If
StartRipple = 1
If (ChangedPageField <> -1) Then
    StartRipple = ChangedPageField + 1
    If ((StrComp(ChangedPf.CurrentPage, "(All)") = 0) And ChangedPageField <> 1) Then
        StartRipple = ChangedPageField
    End If
 
End If
StartRippleFieldName = PageFields(StartRipple)
' identify unique values of each page field using a copy pivot
If (StartRipple <= NPageFields) Then
    Sheets(OriginalWorkSheetName).Copy after:=ActiveSheet
    TempWorkSheetName = ActiveSheet.Name
    Set TempWorkSheet = ActiveSheet
    Set TmpPivot = TempWorkSheet.PivotTables(1)
 
' always need a row field. start with 1st to filter on
    TmpPivot.PivotFields(StartRippleFieldName).Orientation = xlRowField
    KeepRowName = StartRippleFieldName
' prepare the copy. move all fields except one row field to be page
    For i = 1 To SavedNFields
        fieldname = FieldList(i)
        If (fieldname <> StartRippleFieldName) Then
            Select Case FieldType(i)
                Case xlHidden, xlDataField
                    Debug.Print "Should not happen ", FieldList(i)
                Case xlPageField
                    'TmpPivot.PivotFields(fieldname).Orientation = xlPageField
                    Debug.Print ""
                Case xlColumnField ' remove it
                    TmpPivot.PivotFields(fieldname).Orientation = xlHidden
                Case xlRowField ' remove it
                    TmpPivot.PivotFields(fieldname).Orientation = xlHidden
            End Select
        End If
    Next i
' clear the filters further down the ripple
' for this you have to make all the fields visible
    If (NPageFields > StartRipple) Then
        For i = StartRipple + 1 To NPageFields
            Set Thisfield = TmpPivot.PivotFields(PageFields(i))
            For Each item In Thisfield.PivotItems
                Debug.Print item.Name, " vis was ", item.Visible
                item.Visible = True
            Next item
 
           ' TmpPivot.PivotFields(PageFields(i)).PivotItems("(All)").Visible = True
            TmpPivot.PivotFields(PageFields(i)).CurrentPage = "(All)"
            Debug.Print Err.Number
        Next i
    End If
' create a blank row 1
    TempWorkSheet.Rows("1:1").Select
    Selection.Insert Shift:=xlDown
 
    If (NPageFields > ChangedPageField) Then
        For i = StartRipple To NPageFields
            'temp = PageFields(i)
            Call SetFilter(PageFields(i), OriginalPivot, TmpPivot, FieldList, SavedNFields, KeepRowName)
 
        Next i
    End If
End If
ErrorFlag = 0
clearup:
 
  Debug.Assert ErrorFlag = 0
  If (Not Err Is Nothing) Then Debug.Print "error is ", Err.Number
  'TmpPivot.ManualUpdate = False
  If (TempWorkSheetName <> "") Then Sheets(TempWorkSheetName).Delete
  ScratchWorkSheet.Delete
  Sheets(OriginalWorkSheetName).Activate
  On Error GoTo 0
 
  ' probabbly no need for this attempt to ensure currentpage is set
  ' problem was that you cannot set "(all)" unless all items visible
  For i = 1 To NPageFields
    WhichField = -1
    PF = PageFields(i)
    For j = 1 To SavedNFields
        If (PF = FieldList(j)) Then WhichField = j
    Next j
    If WhichField >= 0 Then
        temp = OriginalPivot.PivotFields(FieldList(WhichField)).CurrentPage
        OriginalPivot.PivotFields(FieldList(WhichField)).CurrentPage = SavedFilterValues(WhichField)
        Set Thisfield = OriginalPivot.PivotFields(FieldList(WhichField))
    Else
        Debug.Print "Error"
    End If
  Next i
 
  'OriginalPivot.RefreshTable
 
  If (SUoff) Then Application.ScreenUpdating = True
  Application.EnableEvents = True
  Application.DisplayAlerts = True
 
  For i = 1 To NPageFields
    WhichField = -1
    PF = PageFields(i)
    For j = 1 To SavedNFields
        If (PF = FieldList(j)) Then WhichField = j
    Next j
    If WhichField >= 0 Then
        'OriginalPivot.PivotFields(FieldList(WhichField)).CurrentPage = SavedFilterValues(WhichField)
        Set Thisfield = OriginalPivot.PivotFields(FieldList(WhichField))
    Else
        Debug.Print "Error"
    End If
  Next i
 
  On Error GoTo 0
 
End Sub
' make the temp pivot have fieldname as the sole row field and extract possible values
Private Sub SetFilter(FieldToCustomise As String, ByVal OriginalPivot As PivotTable, TmpPivot As PivotTable, FieldList() As String, NFields As Integer, KeepRowName As String)
 
Dim PossibleValues As Variant
Dim PossibleValueRange As Range
Dim PossibleValueRange2 As Range
Dim FindResult As Range
Dim item As PivotItem
Dim i As Integer
Dim ErrorFlag As Integer
Dim PivotFieldObject As PivotField
Dim NPossibleCells As Integer
Dim ItemValue As String
 
On Error GoTo clearup2
ErrorFlag = 1
Debug.Print "Customising field", FieldToCustomise, " Current Page is ", OriginalPivot.PageFields(FieldToCustomise).CurrentPage
' make FieldToCustomise the only row field
If (FieldToCustomise <> KeepRowName) Then
    TmpPivot.PivotFields(FieldToCustomise).Orientation = xlRowField
    TmpPivot.PivotFields(KeepRowName).Orientation = xlPageField
End If
'TmpPivot.PivotFields(FieldToCustomise).AutoSortOrder = xlManual
TmpPivot.ManualUpdate = True
On Error Resume Next ' skip errors here
For Each item In TmpPivot.PivotFields(FieldToCustomise).PivotItems
    ItemValue = item.Value
    'Debug.Print "Clearing", ItemValue
    item.Visible = True
Next item
TmpPivot.ManualUpdate = False
On Error GoTo clearup2
 
' now have set of possible values - store in PossibleValues
Set PossibleValueRange = TmpPivot.RowRange
NPossibleCells = TmpPivot.RowRange.Count
Set PossibleValueRange2 = Application.Range(PossibleValueRange.Cells(2, 1), PossibleValueRange.Cells(NPossibleCells - 1))
PossibleValueRange2.Select
Selection.Copy
ActiveSheet.Range("A1").Select
Selection.PasteSpecial Paste:=xlPasteValuesAndNumberFormats, Operation:=xlNone, SkipBlanks:=False, Transpose:=True
 
Set PivotFieldObject = OriginalPivot.PivotFields(FieldToCustomise)
 
OriginalPivot.ManualUpdate = True
' now set the list of values to display in the filter drop down (in original pivot)
For Each item In OriginalPivot.PivotFields(FieldToCustomise).PivotItems
    ItemValue = item.Value
    'Debug.Print "PivotFilter item ", itemvalue
    'On Error Resume Next
    Set FindResult = PossibleValueRange2.Find(ItemValue)
    'On Error GoTo Clearup
    If (FindResult Is Nothing) Then
        Debug.Print "Setting ", ItemValue, " invisible"
        If ((ItemValue <> "(blank)") And (ItemValue <> "#N/A") And (Not Len(ItemValue) = 0)) Then item.Visible = False  ' avoid runtime error for item (blank)
    Else
        item.Visible = True
        Debug.Print ItemValue, " set visible"
    End If
Next item
If (FieldToCustomise <> KeepRowName) Then
    TmpPivot.PivotFields(KeepRowName).Orientation = xlRowField
    TmpPivot.PivotFields(FieldToCustomise).Orientation = xlPageField
End If
OriginalPivot.ManualUpdate = False
 
ErrorFlag = 0
clearup2:
    Debug.Print "Finsihed Customising field", FieldToCustomise, " Current Page is ", OriginalPivot.PageFields(FieldToCustomise).CurrentPage
    Debug.Print "Last value was <", ItemValue, ">"
    Debug.Assert ErrorFlag = 0
    Debug.Print Len(ItemValue)
    Debug.Print Err.Number
    On Error GoTo 0
 
End Sub
 
Upvote 0
Hi Mike,

I am interested in using the code you provided. Any insight on how I have to set up my pivot chart/table in order for this to work?

Also, what exactly does this code do for you? I think we have a similar interest here.
 
Upvote 0
I did write something 2.5 years ago but it was never entirely stable. We gave up on it.
If you can use Excel 2010 or 2013 Slicers then that would be my recommendation.
Though I must admit I have never tested them!

The code essentially restricts choices in pivottable page table drop downs based on the choices made in the drop-downs above it.

It is about as intensive VBA code as I intend to try and may not be perfect

Mike
 
Upvote 0

Forum statistics

Threads
1,214,943
Messages
6,122,380
Members
449,080
Latest member
Armadillos

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top