Документация HotXLS

API сводных таблиц классического XLS

HotXLS поддерживает два классических сценария сводных таблиц XLS. Книги, открытые из Excel, сохраняют исходное представление сводной таблицы и записи кэша сводной таблицы для обратного сохранения без потерь. Код также может просматривать типизированную модель сводной таблицы и создавать новые классические сводные таблицы XLS через

Полный цикл и типизированная модель

Когда книга уже содержит сводные таблицы, читатель сохраняет необработанные записи BIFF и также предоставляет типизированную модель по возможности. Таблицы и кэши, загруженные из файла, сохраняют FromRawBlobs = True, поэтому при сохранении оригинальные байты воспроизводятся ради точности. Сводные таблицы, созданные программно, используют типизированный писатель

const
  xlPivotBaseItemPrevious = $7FFB;
  xlPivotBaseItemNext = $7FFC;

type
  TXLSPivotFieldAxis = (xlpfaNone, xlpfaRowField, xlpfaColumnField, xlpfaPageField, xlpfaDataField);
  TXLSPivotAggregation = (xlpaSum, xlpaCount, xlpaAverage, xlpaMax, xlpaMin, xlpaProduct, xlpaCountNums, xlpaStdDev, xlpaStdDevP, xlpaVar, xlpaVarP);
  TXLSPivotShowDataAs = (xlpsdaNormal, xlpsdaDifference, xlpsdaPercent, xlpsdaPercentDiff, xlpsdaRunTotal, xlpsdaPercentOfRow, xlpsdaPercentOfCol, xlpsdaPercentOfTotal, xlpsdaIndex);
  TXLSPivotExtendedShowDataAs = (xlpesdaNone, xlpesdaPercentOfParent, xlpesdaPercentOfParentRow, xlpesdaPercentOfParentColumn, xlpesdaPercentOfRunningTotal, xlpesdaRankAscending, xlpesdaRankDescending);
  TXLSPivotSubtotal = (xlpsDefault, xlpsSum, xlpsCount, xlpsAverage, xlpsMax, xlpsMin, xlpsProduct, xlpsCountNums, xlpsStdDev, xlpsStdDevP, xlpsVar, xlpsVarP);
  TXLSPivotSubtotals = set of TXLSPivotSubtotal;
  TXLSPivotDesignerZone = (xlpdzAvailable, xlpdzRows, xlpdzColumns, xlpdzFilters, xlpdzValues);

  TXLSPivotCacheValue = record
    ValueType: TXLSPivotCacheFieldType;
  end;

  TXLSPivotCacheField = class
    property Name: WideString;
    property FieldType: TXLSPivotCacheFieldType;
    property ItemCount: Integer;
    property Items[Index: Integer]: TXLSPivotCacheItem;
  end;

  TXLSPivotCache = class
    property CacheId: Integer;
    property SourceRangeSheet: WideString;
    property SourceFirstRow: Integer;
    property SourceFirstCol: Integer;
    property SourceLastRow: Integer;
    property SourceLastCol: Integer;
    property RecordCount: Integer;
    property FieldCount: Integer;
    property Fields[Index: Integer]: TXLSPivotCacheField;
    property RecordIndices[RecordIdx, FieldIdx: Integer]: Integer;
    property FromRawBlobs: Boolean;
  end;

  TXLSPivotCaches = class
    function Add: TXLSPivotCache;
    function FindByCacheId(ACacheId: Integer): TXLSPivotCache;
    property Count: Integer;
    property Items[Index: Integer]: TXLSPivotCache;
  end;

  TXLSPivotField = class
    function AddItem: TXLSPivotItem;
    function AddCalculatedItem(const AName, AFormula: WideString): TXLSPivotCalculatedItem;
    property Name: WideString;
    property Axis: TXLSPivotFieldAxis;
    property CacheFieldIndex: Integer;
    property ItemCount: Integer;
    property Items[Index: Integer]: TXLSPivotItem;
    property SubtotalMask: Word;
    property Subtotals: TXLSPivotSubtotals;
    property PageItemIndex: Integer;
    property SortType: TXLSPivotFieldSortType;
    property SortDataField: Integer;
    property Filters: TXLSPivotFilters;
  end;

  TXLSPivotDataField = class
    property Field: TXLSPivotField;
    property Aggregation: TXLSPivotAggregation;
    property NumberFormat: Word;
    property DisplayName: WideString;
    property ShowDataAs: TXLSPivotShowDataAs;
    property ExtendedShowDataAs: TXLSPivotExtendedShowDataAs;
    property BaseField: Integer;
    property BaseItem: Integer;
  end;

  TXLSPivotSupplementalRecord = class
    property RecID: Word;
    property BodyLength: Integer;
    property Body[Index: Integer]: Byte;
  end;

  TXLSPivotTable = class
    function AddField(const AName: WideString; ACacheFieldIndex: Integer): TXLSPivotField;
    function FindFieldByName(const AName: WideString): TXLSPivotField;
    function SetFieldAxis(const AName: WideString; AAxis: TXLSPivotFieldAxis): TXLSPivotField;
    function AddRowField(const AName: WideString): TXLSPivotField;
    function AddColumnField(const AName: WideString): TXLSPivotField;
    function AddPageField(const AName: WideString): TXLSPivotField;
    function AddDataField(AField: TXLSPivotField; AAggregation: TXLSPivotAggregation): TXLSPivotDataField;
    function AddDataFieldByName(const AName: WideString; AAggregation: TXLSPivotAggregation = xlpaSum): TXLSPivotDataField;
    function AddCalculatedMember(const AName, AFormula: WideString): TXLSPivotCalculatedMember;
    function AddSupplementalRecord(ARecID: Word; const ABody: TBytes): TXLSPivotSupplementalRecord;
    function Make(AWriter: TlxPivotResultWriter): Integer;
    property Name: WideString;
    property DataCaption: WideString;
    property CacheId: Integer;
    property Cache: TXLSPivotCache;
    property FieldCount: Integer;
    property Fields[Index: Integer]: TXLSPivotField;
    property DataFieldCount: Integer;
    property DataFields[Index: Integer]: TXLSPivotDataField;
    property RowGrandTotals: Boolean;
    property ColumnGrandTotals: Boolean;
    property MaterializeGrandTotalRow: Boolean;
    property MaterializeGrandTotalColumn: Boolean;
    property SupplementalRecordCount: Integer;
    property SupplementalRecords[Index: Integer]: TXLSPivotSupplementalRecord;
    property FromRawBlobs: Boolean;
  end;

  TXLSPivotDesigner = class
    constructor Create(APivotTable: TXLSPivotTable = nil);
    procedure ClearLayout;
    function MoveField(const AName: WideString; AZone: TXLSPivotDesignerZone; APosition: Integer = -1): Boolean;
    function SetAggregation(const AName: WideString; AAggregation: TXLSPivotAggregation): Boolean;
    function Validate(out ErrorText: WideString): Boolean;
    function Apply(AWriter: TlxPivotResultWriter): Integer;
    property PivotTable: TXLSPivotTable;
    property FieldCount: Integer;
    property Fields[Index: Integer]: TXLSPivotDesignerField;
  end;

  TXLSPivotTables = class
    function Add: TXLSPivotTable;
    function FindByName(const AName: WideString): TXLSPivotTable;
    property Count: Integer;
    property Items[Index: Integer]: TXLSPivotTable;
  end;
      

Точки входа листа и рабочей книги

type
  TXLSWorksheet = class
    function AddPivotTable(const SourceRangeA1: WideString; DestRow, DestCol: Integer; const Name: WideString): TXLSPivotTable;
    function PivotSetDataFieldFormat(DataField: TXLSPivotDataField; const FormatStr: WideString): Word;
    property PivotTables: TXLSPivotTables;
  end;

  TXLSWorkbook = class
    property PivotCaches: TXLSPivotCaches;
    function RegisterNumFormat(const FormatStr: WideString): Word;
end;
      

RegisterNumFormat регистрирует строку числового формата и возвращает её BIFF8-индекс ifmt — именно его ожидают TXLSPivotDataField.NumberFormat и TXLSPivotCacheField.NumberFormat; повторная регистрация той же строки возвращает тот же индекс

DestRow и DestCol отсчитываются с единицы, как Cells[Row, Col], поэтому AddPivotTable('Data!A1:C9', 3, 1, 'SalesPivot') привязывает сводную таблицу к ячейке A3. Позиции в TXLSPivotTable (FirstRow .. FirstDataCol) и исходный диапазон в TXLSPivotCache тоже отсчитываются с единицы — точно так же, как в движке XLSX; якорь за пределами листа 65,536 x 256 возвращает nil

Построение drag-and-drop компоновки сводной таблицы

TXLSPivotDesigner — невизуальная модель компоновки, которая может стоять за интерфейсами списков полей VCL, FMX, веб или командной строки, не привязывая логику сводных таблиц к одному UI-тулкиту

Каждое поле сообщает зону (доступная, строка, столбец, фильтр или значение) и свою детерминированную позицию; MoveField выполняет перетаскивание, SetAggregation меняет поле значений, Validate сообщает о неполных компоновках, а Apply валидирует и материализует результат через обычный писатель сводных таблиц

Новые числовые поля значений и поля дат по умолчанию используют xlpaSum, тогда как текстовые, логические, ошибочные, пустые и смешанные поля по умолчанию используют xlpaCount

var
  Designer: TXLSPivotDesigner;
  ErrorText: WideString;
begin
  Designer := TXLSPivotDesigner.Create(Pivot);
  try
    Designer.MoveField('Region', xlpdzRows);
    Designer.MoveField('Quarter', xlpdzColumns);
    Designer.MoveField('Closed', xlpdzFilters);
    Designer.MoveField('Revenue', xlpdzValues);
    Designer.SetAggregation('Revenue', xlpaAverage);
    if Designer.Validate(ErrorText) then
      Designer.Apply(ResultWriter.WriteCell);
  finally
    Designer.Free;
  end;
end;
      

Создание сводной таблицы

uses lxHandle;

var
  Workbook: TXLSWorkbook;
  SourceSheet, PivotSheet: TXLSWorksheet;
  Pivot: TXLSPivotTable;
  RowField: TXLSPivotField;
  DataField: TXLSPivotDataField;
begin
  Workbook := TXLSWorkbook.Create;
  try
    SourceSheet := Workbook.Sheets[1];
    SourceSheet.Name := 'SalesData';
    SourceSheet.Cells[1, 1].Value := 'Region';
    SourceSheet.Cells[1, 2].Value := 'Product';
    SourceSheet.Cells[1, 3].Value := 'Revenue';

    PivotSheet := Workbook.Sheets.Add;
    PivotSheet.Name := 'PivotReport';
    Pivot := PivotSheet.AddPivotTable('SalesData!A1:C20', 4, 1, 'SalesPivot');
    RowField := Pivot.AddRowField('Region');
    Pivot.AddColumnField('Product');
    DataField := Pivot.AddDataFieldByName('Revenue', xlpaSum);
    RowField.Subtotals := [xlpsSum];
    PivotSheet.PivotSetDataFieldFormat(DataField, '$#,##0.00');

    Workbook.SaveAs('pivot-report.xls');
  finally
    Workbook.Free;
  end;
end;
      

Отображение значений как

Установите ShowDataAs у каждого поля данных перед вызовом Make или сохранением книги. Для Difference, Percent и PercentDiff нужен индекс BaseField поля сводной таблицы (с нулевой базой) плюс BaseItem. Для RunTotal достаточно одного BaseField. Режимы «строка», «столбец», «итог» и «индекс» берут знаменатели из исходных записей с настроенной агрегацией

Явный BaseItem — это индекс элемента с нулевой базой внутри базового поля сводной таблицы. Используйте xlPivotBaseItemPrevious или xlPivotBaseItemNext для сравнения соседних элементов. Отсутствующие пересечения для сравнения остаются пустыми, а присутствующий нулевой знаменатель даёт #DIV/0!

DataField := Pivot.AddDataFieldByName('Revenue', xlpaSum);
DataField.ShowDataAs := xlpsdaPercentDiff;
DataField.BaseField := 0;
DataField.BaseItem := xlPivotBaseItemPrevious;
CellsWritten := Pivot.Make(ResultWriter.WriteCell);
      

Фильтрация, группировка и сортировка

Make применяет скрытые элементы и выборы страничных полей перед построением домена, затем оценивает фильтры по подписи, дате, количеству, процентам, сумме и значению. Числовые диапазоны, календарные группы и дискретные группы используют метаданные группировки полей кэша до агрегации. SortType выбирает стабильное упорядочивание по подписям, а SortDataField задаёт поле значений с нулевой базой для стабильного упорядочивания значений данных

Вычисляемые выражения

Вычисляемые поля кэша выполняются один раз на исходную запись, вычисляемые элементы — по агрегатам соседних элементов, а вычисляемые члены с MemberType = 'data' добавляют виртуальные поля значений после агрегации источника. Имена формул нечувствительны к регистру и могут быть в квадратных скобках или кавычках. Поддерживаются арифметика, сравнения, проценты, скобки и распространённые числовые и логические функции, с повторными проходами зависимостей для ациклических вычисляемых определений

ProfitField := Pivot.Cache.AddField('Profit', xlpcftNumber);
ProfitField.Formula := '=Revenue-Cost';
ProfitField.DatabaseField := False;
Pivot.AddDataField(Pivot.AddField('Profit', 4), xlpaSum);

RegionField.AddCalculatedItem('Combined', '=North+South');
Pivot.AddCalculatedMember('Margin', '=Profit/Revenue').MemberType := 'data';
      

Промежуточные и общие итоги

Каждое поле строк или столбцов может материализовать любую настроенную функцию промежуточных итогов выше или ниже своих детальных элементов через Subtotals и SubtotalTop. RowGrandTotals и ColumnGrandTotals управляют политикой, сохраняемой в книге, а MaterializeGrandTotalRow и MaterializeGrandTotalColumn позволяют result-callback'у включать явный вывод общих итогов без повторного прохода по исходным записям

Расширенные режимы отображения значений

ExtendedShowDataAs предоставляет «процент от родителя», «процент от строки родителя», «процент от столбца родителя», «процент от нарастающего итога» и ранг по возрастанию или убыванию. Эти режимы используют модель расширений XLSX и не выдают неподдерживаемые значения полей данных классического BIFF

Просмотр существующей рабочей книги со сводными таблицами

Workbook.Open('excel-authored-pivot.xls');
for SheetIndex := 1 to Workbook.Sheets.Count do
begin
  Sheet := Workbook.Sheets[SheetIndex];
  for PivotIndex := 0 to Sheet.PivotTables.Count - 1 do
  begin
    Pivot := Sheet.PivotTables[PivotIndex];
    Writeln(Pivot.Name);
    Writeln(Pivot.FieldCount);
    if Assigned(Pivot.Cache) then
      Writeln(Pivot.Cache.SourceRangeSheet);
  end;
end;
      

Связанные разделы