Как я могу препятствовать тому, чтобы PivotChart стал обычной диаграммой на листе?

Используя Excel 2010, я написал несколько VBA, чтобы скопировать выбранные листы из основной книги в рабочую книгу клиента. Код работает очень хорошо, чтобы скопировать лист данных с данными и сводной таблицей (-ами), связанными с данными, и лист диаграммы с одним или несколькими PivotCharts в новую книгу.

Проблема в том, что в рабочей книге назначения диаграммы больше не являются PivotCharts, они являются регулярными диаграммами, а их исходный диапазон данных пуст. Исходные данные для Master PivotChart заполнены, но выделены серым цветом, поэтому они не редактируются.

Проблема появляется сразу после копирования листа из одной книги в другую (в этой строке: XLMaster.Sheets(SlideRS.Fields(2).Value).Copy After:=XLClinic.Sheets(XLClinic.Sheets.Count)), хотя я буду включать код для вызова Subs. К тому времени, когда он дойдет до этих строк, график уже сломан.

Вопрос состоит из трех частей:

  • Могу ли я предотвратить преобразование PivotChart в обычный график в копии? т.е. существует ли флаг/параметр для .Copy, который я пропустил?
  • Если нет, могу ли я обновить диаграмму как PivotChart снова чем-то вдоль строк cchart.Chart.PivotLayout.PivotTable = mchart.Chart.PivotLayout.PivotTable?
  • Неудача любого из них, что лучший способ создать PivotChart с нуля в скопированном листе?

ПРИМЕЧАНИЯ:

  • Это стандартные сводные таблицы Excel, я не использую PowerPivot. Однако я открыт для этого, если это исправит проблему. Сделав небольшое чтение на PowerPivot, я не думаю, что это поможет мне, но, опять же, я открыт для предложений.
  • В ответ на комментарий Jens, исходные данные и сводные таблицы находятся на одном листе, а сводные диаграммы находятся на втором листе.

Вот код. Он отлично работает для всего, кроме копирования листа с PivotChart без изменений в качестве PivotChart.

  While Not SlideRS.EOF                                                   'loop through all the supporting data sheets for this graph sheet
    If SlideRS.Fields(1) <> SlideRS.Fields(2) Then                        'the worksheet depends on something else, copy it first
      If InStr(1, UsedSlides, SlideRS.Fields(2)) = 0 Then                 'if the depended upon slide is not in the list of UsedSlides, then add it
        Form_Master.ProcessStatus.Value = "Processing: " & ClinicName & "  Slide: " & SlideRS!SlideName & "    Worksheet: " & SlideRS.Fields(2).Value
        XLMaster.Sheets(SlideRS.Fields(2).Value).Copy After:=XLClinic.Sheets(XLClinic.Sheets.Count)
        Set NewSheet = XLClinic.Sheets(XLClinic.Sheets.Count)
        UsedSlides = UsedSlides & "," & NewSheet.Name
        UpdateCharts XLMaster.Sheets(SlideRS.Fields(2).Value), NewSheet
        ProcessDataSheet NewSheet, NewXLName
        Set NewSheet = Nothing
      End If
    End If
    SlideRS.MoveNext                                                      'move to the next record of supporting Data sheets
  Wend

Вот код для UpdateCharts. Его цель - скопировать цвета с листа "Мастер" на "Клиент", поскольку Excel, похоже, предпочитает назначать случайные цвета для новых диаграмм.

Private Sub UpdateCharts(ByRef Master As Worksheet, ByRef Clinic As Worksheet)

Dim MChart As Excel.ChartObject
Dim CChart As Excel.ChartObject
Dim Ser As Excel.Series
Dim pnt As Excel.point
Dim i As Integer
Dim Color() As Long
Dim ColorWheel As ChartColors

  Set ColorWheel = New ChartColors
  For Each MChart In Master.ChartObjects
    For Each CChart In Clinic.ChartObjects
      If CChart.Name = MChart.Name Then
        If CChart.Chart.ChartType = xlPie Or _
           CChart.Chart.ChartType = xl3DPie Or _
           CChart.Chart.ChartType = xl3DPieExploded Or _
           CChart.Chart.ChartType = xlPieExploded Or _
           CChart.Chart.ChartType = xlPieOfPie Then
          If InStr(1, CChart.Name, "ColorWheel") Then                  'this pie chart needs to have pre-defined colors assigned
            i = 1
            For Each pnt In CChart.Chart.SeriesCollection(1).Points
              pnt.Format.Fill.ForeColor.RGB = ColorWheel.GetRGB("Pie" & i)
              i = i + 1
            Next
          Else                                                      'just copy the colors from XLMaster
            'collect the colors for each point in the SINGLE series in the MASTER pie chart
            i = 0
            For Each Ser In MChart.Chart.SeriesCollection
              For Each pnt In Ser.Points
                ReDim Preserve Color(i)
                Color(i) = pnt.Format.Fill.ForeColor.RGB
                i = i + 1
              Next 'point
            Next 'series
            'take that collection of colors and apply them to the CLINIC pie chart points
            i = 0
            For Each Ser In CChart.Chart.SeriesCollection
              For Each pnt In Ser.Points
                pnt.Format.Fill.ForeColor.RGB = Color(i)
                i = i + 1
              Next 'point
            Next 'series
          End If
        Else
          'get the series colors from the MASTER
          i = 0
          For Each Ser In MChart.Chart.SeriesCollection
            ReDim Preserve Color(i)
            Color(i) = Ser.Interior.Color
            i = i + 1
          Next 'series
          'assign them to the CLINIC
          i = 0
          For Each Ser In CChart.Chart.SeriesCollection
            Ser.Interior.Color = Color(i)
            i = i + 1
          Next 'series
        End If 'pie chart
      End If 'clinic chart = master chart
    Next 'clinic chart
  Next 'master chart
  Set ColorWheel = Nothing

End Sub

Вот код ProcessDataSheet(). Это обновит данные на листе на основе одного или нескольких SQL-запросов, встроенных в лист.

Private Sub ProcessDataSheet(ByRef NewSheet As Excel.Worksheet, ByRef NewXLName As String)

Const InstCountRow As Integer = 1
Const InstCountCol As Integer = 2
Const InstDataCol As Integer = 2
Const InstCol As Integer = 3
Const ClinicNameParm As String = "{ClinicName}"
Const LikeClinicName As String = "{LikeClinicName}"
Const StartDateParm As String = "{StartDate}"
Const EndDateParm As String = "{EndDate}"
Const LocIDParm As String = "{ClinicLoc}"

Dim Data As New ADODB.Recordset
Dim InstCount As Integer
Dim SQLString As String
Dim Inst As Integer
Dim pt As Excel.PivotTable
Dim Rng As Excel.Range
Dim Formula As String
Dim SChar As Integer
Dim EChar As Integer
Dim Bracket As Integer
Dim TabName As String
Dim RowCol() As String

  'loop through all the instructions on the page and update the appropriate data tables
  InstCount = NewSheet.Cells(InstCountRow, InstCountCol)
  For Inst = 1 To InstCount
    RowCol = Split(NewSheet.Cells(InstCountRow + Inst, InstDataCol), ",")
    SQLString = NewSheet.Cells(InstCountRow + Inst, InstCol)
    SQLString = Replace(SQLString, """", "'")
    If InStr(1, SQLString, LikeClinicName) > 0 Then
      SQLString = Replace(SQLString, LikeClinicName, "'" & ClinicSystoc & "%'")
    Else
      SQLString = Replace(SQLString, ClinicNameParm, "'" & ClinicSystoc & "'")
    End If
    SQLString = Replace(SQLString, LocIDParm, "'" & ClinicLocID & "%'")
    SQLString = Replace(SQLString, StartDateParm, "#" & StartDate & "#")
    SQLString = Replace(SQLString, EndDateParm, "#" & EndDate & "#")
    Data.Open Source:=SQLString, ActiveConnection:=CurrentProject.Connection
    If Not Data.EOF And Not Data.BOF Then
      NewSheet.Cells(CInt(RowCol(0)), CInt(RowCol(1))).CopyFromRecordset Data
    End If
    Data.Close
  Next

  'search for all external sheet refrences and truncate them so it points to *this* worksheet
  Set Rng = NewSheet.Range(NewSheet.Cells.Address).Find(What:=XLMasterFileName, LookIn:=xlFormulas, Lookat:=xlPart, MatchCase:=False)
  While Not Rng Is Nothing
    Formula = Rng.Cells(1, 1).Formula
    If InStr(1, Formula, "'") > 0 Then
      SChar = InStr(1, Formula, "'")
      EChar = InStr(SChar + 1, Formula, "'")
      Bracket = InStr(1, Formula, "]")
      TabName = Mid(Formula, Bracket + 1, EChar - Bracket - 1)
      Rng.Replace What:=Mid(Formula, SChar, EChar - SChar + 1), replacement:=TabName, Lookat:=xlPart
    End If
    Set Rng = NewSheet.Range(NewSheet.Cells.Address).Find(What:=XLMasterFileName, LookIn:=xlFormulas, Lookat:=xlPart, MatchCase:=False)
  Wend
  Set Rng = Nothing

  'fix all the pivot table data sources so they point to *this* spreadsheet
  'TODO: add a filter in here to remove blanks
  'NOTE: do I want to add for all pivots, or only selected ones?
  For Each pt In NewSheet.PivotTables
    Formula = pt.PivotCache.SourceData
    Bracket = InStr(1, Formula, "!")
    Formula = Right(Formula, Len(Formula) - Bracket)
    pt.ChangePivotCache XLClinic.PivotCaches.Create(SourceType:=xlDatabase, SourceData:=Formula)
  Next

  SaveNewXL NewXLName         'yes, save the spreadsheet every single time so that the links in the PPT can be updated to point to it.  Sigh...

End Sub

UPDATE

Основываясь на предложении R3uK, я добавил вызов в начало UpdateCharts Sub, здесь:

  If Master.ChartObjects.Count > 0 Then
    Set ColorWheel = New ChartColors      'only do this if we need to
  End If
  For Each MChart In Master.ChartObjects
    If Not MChart.Chart.PivotLayout Is Nothing Then

      'Re-copy just the pivot chart from Master to Clinic
      CopyPivotChart PivotItemsList, MChart, CChart, Clinic

    End If

С CopyPivotChart здесь:

Private Sub CopydPivotChart(ByVal PivotItemsList As PivotTableItems, ByVal MChart As Excel.ChartObject, ByRef CChart As Excel.ChartObject, ByRef Clinic As Worksheet)

Dim TChart As Excel.ChartObject

'Breakpoint 1
  For Each TChart In Clinic.ChartObjects
    If TChart.Name = MChart.Name Then
      TChart.Delete
    End If
  Next

  MChart.Chart.ChartArea.Copy
'Breakpoint 2
  Clinic.PasteSpecial Format:="Microsoft Office Drawing Object", Link:=False, DisplayAsIcon:=False
  Clinic.PasteSpecial Format:=2

End Sub

Когда я запустил этот код, теперь я получаю

Ошибка времени выполнения "1004": не удалось выполнить метод "PasteSpecial" объекта "Рабочий лист"

в строке, следующей за Breakpoint 2.

Теперь, если я пропущу цикл For Each в Breakpoint 1 (вручную перетащите точку выполнения ниже цикла) и вручную удалите диаграмму из Worksheet Clinic, тогда код будет выполнен просто отлично.

Ответ 1

Я решил проблему, используя шаблон с поддержкой макросов .XLTM в качестве фактического шаблона!

Вместо того, чтобы открывать файл шаблона, копируя рабочие листы, необходимые из шаблона, в новую книгу, я теперь открываю .XLTM, удаляю листы, которые не нужны для конкретного отчета клиента. Это полностью устранило необходимость копирования листов, диаграмм и графиков и удалило все ошибки, созданные при попытке сделать это.

Это не позволяет конкретно решить проблему копирования сводной диаграммы, не теряя при этом своей четкости, но она решила проблему с большей картиной, как это сделать (я сказал, что я был открыт для альтернативных предложений).

Ответ 2

1. В моем скромном опыте копирования Pivot Chart я не копировал лист, но Chart:

Sheets("Graph1").ActiveChart.ChartArea.Copy
ActiveSheet.PasteSpecial Format:="Objet Dessin Microsoft Office", _
    Link:=True, DisplayAsIcon:=False

Вы пытались создать пустую страницу и вставить в нее диаграмму? Вам, вероятно, придется изменить формат, который на французском языке, но это должно сделать трюк!

2. Нет подсказки....

3. Для создания сводной таблицы с нуля у меня нет магических трюков, но я использую это как шаблон:

Sub Create_DCT(ByVal Source_Table_Name As String, ByVal DCT_Sheet_Name As String, ByVal DCT_Name As String)


    DeleteAndAddSheet DCT_Sheet_Name

    ActiveWorkbook.PivotCaches.Create(SourceType:=xlDatabase, _
        SourceData:=Source_Table_Name, _
        Version:=xlPivotTableVersion14). _
        CreatePivotTable _
        TableDestination:=DCT_Sheet_Name & "!R3C1", _
        TableName:=DCT_Name, _
        DefaultVersion:=xlPivotTableVersion14


End Sub

Sub Add_Fields_DCT(ByVal DCT_Sheet_Name As String, ByVal DCT_Name As String)
    Dim Ws As Worksheet
    Set Ws = Worksheets(DCT_Sheet_Name)

    'Organized filters
    With Ws.PivotTables(DCT_Name).PivotFields("Cluster")
        .Orientation = xlPageField
        .Position = 1
    End With
    With Ws.PivotTables(DCT_Name).PivotFields("Region")
        .Orientation = xlPageField
        .Position = 2
    End With
    With Ws.PivotTables(DCT_Name).PivotFields("Account")
        .Orientation = xlPageField
        .Position = 3

    'Organized rows
    With Ws.PivotTables(DCT_Name).PivotFields("Family")
        .Orientation = xlRowField
        .Position = 1
    End With
    With Ws.PivotTables(DCT_Name).PivotFields("Sub_family")
        .Orientation = xlRowField
        .Position = 2
    End With
    With Ws.PivotTables(DCT_Name).PivotFields("Invoice_Country")
        .Orientation = xlRowField
        .Position = 3
    End With
    With Ws.PivotTables(DCT_Name).PivotFields("Product")
        .Orientation = xlRowField
        .Position = 4
    End With

    'Columns : none
'    With Ws.PivotTables(DCT_Name).PivotFields("Family")
'        .Orientation = xlColumnField
'        .Position = 1
'    End With


'Data fields (adding, modifying, formatting and compacting)
    'Data fiels : Adding
    'With Ws.PivotTables(DCT_Name).AddDataField Ws.PivotTables(DCT_Name)
        Ws.PivotTables(DCT_Name).AddDataField Ws.PivotTables(DCT_Name).PivotFields("Quantity"), "Total Qty", xlSum
        Ws.PivotTables(DCT_Name).AddDataField Ws.PivotTables(DCT_Name).PivotFields("Quantity"), "Avg Qty", xlAverage
        Ws.PivotTables(DCT_Name).AddDataField Ws.PivotTables(DCT_Name).PivotFields("Quantity"), "Qty of Orders", xlCount
        Ws.PivotTables(DCT_Name).AddDataField Ws.PivotTables(DCT_Name).PivotFields("TotalAmountEUR"), "TO (€)", xlSum
        Ws.PivotTables(DCT_Name).AddDataField Ws.PivotTables(DCT_Name).PivotFields("UPL"), "Avg UPL", xlAverage
        Ws.PivotTables(DCT_Name).AddDataField Ws.PivotTables(DCT_Name).PivotFields("Discount"), "Avg Discount", xlAverage
        Ws.PivotTables(DCT_Name).AddDataField Ws.PivotTables(DCT_Name).PivotFields("Discount"), "Min Discount", xlMin
        Ws.PivotTables(DCT_Name).AddDataField Ws.PivotTables(DCT_Name).PivotFields("Discount"), "Max Discount", xlMax
        Ws.PivotTables(DCT_Name).AddDataField Ws.PivotTables(DCT_Name).PivotFields("PVU"), "Min PVU", xlMin
        Ws.PivotTables(DCT_Name).AddDataField Ws.PivotTables(DCT_Name).PivotFields("PVU"), "Max PVU", xlMax
        Ws.PivotTables(DCT_Name).AddDataField Ws.PivotTables(DCT_Name).PivotFields("(PVU-PRI)/PVU"), "Gross Margin", xlAverage
        Ws.PivotTables(DCT_Name).AddDataField Ws.PivotTables(DCT_Name).PivotFields("(PVU-TC)/PVU"), "Net Margin", xlAverage
        Ws.PivotTables(DCT_Name).AddDataField Ws.PivotTables(DCT_Name).PivotFields("PVU-PRI"), "Gross Profit (€)", xlSum
        Ws.PivotTables(DCT_Name).AddDataField Ws.PivotTables(DCT_Name).PivotFields("PVU-TC"), "Net Profit (€)", xlSum
    'End With

    'Data fiels : Modifying
'    With Ws.PivotTables(DCT_Name).PivotFields("Somme de Quantity")
'        .Caption = "Moyenne de Quantity"
'        .Function = xlAverage
'    End With



    'Data formatting
    With ActiveSheet.PivotTables(DCT_Name)
        .PivotFields("Total Qty").NumberFormat = "# ##0"
        .PivotFields("Avg Qty").NumberFormat = "# ##0,#"
        .PivotFields("Qty of Orders").NumberFormat = "# ##0"
        .PivotFields("TO (€)").NumberFormat = "# ##0 €"
        .PivotFields("Avg UPL").NumberFormat = "# ##0 €"
        .PivotFields("Avg Discount").NumberFormat = "0,0%"
        .PivotFields("Min Discount").NumberFormat = "0,0%"
        .PivotFields("Max Discount").NumberFormat = "0,0%"
        .PivotFields("Min PVU").NumberFormat = "# ##0 €"
        .PivotFields("Max PVU").NumberFormat = "# ##0 €"
        .PivotFields("Gross Margin").NumberFormat = "0,0%"
        .PivotFields("Net Margin").NumberFormat = "0,0%"
        .PivotFields("Gross Profit (€)").NumberFormat = "# ##0 €"
        .PivotFields("Net Profit (€)").NumberFormat = "# ##0 €"
    End With


'Compact row fields to minimum
For Each PivIt In ActiveSheet.PivotTables(DCT_Name).PivotFields("Sub_family").PivotItems
    PivIt.DrillTo "Invoice_Country"
Next PivIt

For Each PivIt In ActiveSheet.PivotTables(DCT_Name).PivotFields("Family").PivotItems
    PivIt.DrillTo "Sub_family"
Next PivIt

For Each PivIt In ActiveSheet.PivotTables(DCT_Name).PivotFields("Family").PivotItems
    PivIt.DrillTo "Family"
Next PivIt

End Sub

И мое пользовательское fucntion DeleteAndAddSheet:

Public Function DeleteAndAddSheet(ByVal SheetName As String) As Worksheet

For Each aShe In Sheets
    If aShe.Name <> SheetName Then
    Else
        Application.DisplayAlerts = False
        aShe.Delete
        Application.DisplayAlerts = True
        Exit For
    End If
Next aShe

Sheets.Add After:=Sheets(Sheets.Count)
Sheets(Sheets.Count).Name = SheetName

Set DeleteAndAddSheet = ThisWorkbook.Worksheets(Worksheets.Count)

End Function

Надеюсь, это поможет вам!

Ответ 3

Я предлагаю создать вторую книгу, связанную с листом данных источника, а затем создать соответствующие сводные таблицы во втором листе (оба по существу используют одни и те же данные для заполнения). Я не уверен в следующем пункте, клиент должен иметь связанную версию диаграмм или это чисто для автоматической отчетности?

Если это для автоматической отчетности, я бы предложил новый подход полностью, используя текущую рабочую книгу, которую вы имеете, - создайте макрос, который будет запускаться, когда это необходимо, в режиме времени, ежедневно/еженедельно/ежемесячно и т.д. и отправляйте диаграммы (из листов, которые вы выберите только) в формате pdf - у меня есть пример кода для этого, если требуется:)

Ответ 4

Если вы скопируете лист с данными и листом с диаграммой, связанной с этими данными, скопированная диаграмма не будет ссылаться на данные на скопированном листе, если только листы не будут скопированы вместе за одну операцию. Похоже, что ваш код сначала копирует лист с помощью сводной таблицы, а затем отдельно копирует лист с диаграммой (лист диаграммы или рабочий лист со встроенной диаграммой, не имеет значения). График теряет связь со сводной таблицей в исходной книге и становится регулярной диаграммой с жестко заданными значениями.

Перепишите код, чтобы скопировать оба листа в одну операцию. Затем выполните настройки сводной таблицы и диаграммы в новой книге.