CrossTableFormula

Translates cross-table specifications into concrete Excel formulas and writes them into the cells of analysis worksheets. Each instance is bound to one ICrossTable and its companion IFormulaData, and the central AddFormulas method dispatches to type-specific branches (GlobalSummary, Univariate, Bivariate, TimeSeries, SpatioTemporal, Spatial) to produce COUNTIFS / SUMPRODUCT-style array formulas, percentage columns, number formatting, time-period scaffolding, spatial lookups, and data-validation dropdowns. Consumers obtain an instance through the predeclared Create factory and interact via the ICrossTableFormula interface.

Depends on: ICrossTable, IFormulaData, IFormulas, Formulas, IFormulaCondition, FormulaCondition, ITableSpecs, ISpatialTables, SpatialTables, BetterArray

Version: 1.0 (2026-02-10)

Instantiation

create #

Create a CrossTableFormula instance bound to a cross-table and formula data source.

Signature:

Public Function Create(ByVal tabl As ICrossTable, _
                       ByVal fData As IFormulaData) As ICrossTableFormula

Factory method that returns a new CrossTableFormula through the predeclared-id pattern. Both parameters are validated for Nothing before the instance is constructed. The returned object exposes the ICrossTableFormula interface so callers program against the contract rather than the concrete class.

Parameters:

  • tabl: ICrossTable. The already-constructed cross-table layout providing named ranges, headers, and worksheet reference.
  • fData: IFormulaData. The dictionary-based lookup object supplying variable names, ranges, and formula templates.

Returns: ICrossTableFormula. A fully initialised instance ready to write formulas.

Throws:

  • InvalidArgument When tabl is Nothing.
  • InvalidArgument When fData is Nothing.

Shortcut Properties

valid #

Whether the formula object is valid for the analysis context.

Signature:

Private Property Get Valid() As Boolean

Checks whether the formula engine considers this table formula valid for analysis use. Validity is determined by the IFormulas.Valid method with the "analysis" formula type. A formula may be invalid if the summary function is unsupported, required variables are missing from the dictionary, or the formula template cannot be parsed. When Valid returns False, the AddFormulas method exits immediately without writing any formulas to the worksheet.

Returns: Boolean. True if the formula can be used for analysis-scope generation.


Core Operations

add-formulas #

Write all formulas to the cross-table on the worksheet.

Signature:

Private Sub AddFormulas()

The main formula-writing method that dispatches by table scope. The main entry point that writes all Excel formulas into the cross-table worksheet cells. It reads the table scope from Specifications and dispatches to one of five branches, each handling a fundamentally different table layout: ScopeGlobalSummary writes two unfiltered linelist formulas into a single row; ScopeUnivariate writes a one-variable frequency table with missing/total/percentage support; ScopeBivariate writes a two-variable cross-tabulation grid with column loops and corner cells; ScopeTimeSeries and ScopeSpatioTemporal set up time-period scaffolding (date labels, validation, dropdowns) then write time-bounded conditions wrapped in blank-when-empty guards; ScopeSpatial sets up geographic or health-facility lookups, validation dropdowns, ordering, and registers the table with SpatialTables for refresh operations. The method exits immediately if Valid returns False.


Internal members (not exported)

Internal Properties

self #

Current object instance cast to the interface.

Signature:

Public Property Get Self() As ICrossTableFormula

Properties used during factory construction and internal wiring. Convenience accessor so the Create factory can return the newly constructed object through the ICrossTableFormula interface.

Returns: ICrossTableFormula. The current instance.


table-get #

ICrossTable backing store.

Signature:

Public Property Get Table() As ICrossTable

Returns the cross-table object that this formula writer targets. The cross-table provides the worksheet reference, named ranges, and layout metadata needed to place formulas.

Returns: ICrossTable. The bound cross-table instance.


table-set #

Assign the ICrossTable backing store.

Signature:

Public Property Set Table(ByVal tabl As ICrossTable)

Parameters:


form-data-get #

IFormulaData backing store.

Signature:

Public Property Get formData() As IFormulaData

Returns the formula-data object that supplies dictionary-based lookups for variable names, linelist ranges, and formula templates during generation.

Returns: IFormulaData. The bound formula-data instance.


form-data-set #

Assign the IFormulaData backing store.

Signature:

Public Property Set formData(ByVal fData As IFormulaData)

Parameters:


Shortcut Properties

specifications #

ITableSpecs shortcut for the cross-table specifications.

Signature:

Private Property Get Specifications() As ITableSpecs

Convenience accessors that delegate to the cross-table or its specifications, reducing repetitive chaining throughout the class. Provides a shortcut to the ITableSpecs object associated with this cross-table. ITableSpecs encapsulates all configuration metadata for a table: its row/column variable names, summary function, table scope, missing/total/percentage flags, and the section it belongs to. This property avoids repeatedly chaining Table.Specifications throughout the class methods.

Returns: ITableSpecs. The specification dictionary for the underlying cross-table.


summary-function #

Summary function name from the table specifications.

Signature:

Private Property Get SummaryFunction() As String

Returns the summary function name string from the table specifications. The summary function determines the type of aggregation Excel will perform in the generated formulas. Common values include "N" (count non-empty), "N()" (count all), "SUM", "MEAN", "MEDIAN", and "MIN"/"MAX". This value drives formula generation logic throughout the class, especially in AddNonEmptyTableConditions where "N"/"N()" use a different operator pattern.

Returns: String. A value such as "N", "N()", "SUM", "MEAN", etc.


id #

Unique identifier string for this cross-table.

Signature:

Private Property Get ID() As String

Returns the unique identifier string for this cross-table. The table ID is used extensively as a suffix for named ranges on the analysis worksheet (e.g., "ROW_CATEGORIES_", "VALUES_COL_1_", "TOTAL_ROW_VALUES_"). Every named range that belongs to this table is suffixed with this ID, making it the primary key for locating table regions on the worksheet.

Returns: String. The table identifier unique within the analysis workbook.


section-id #

Section identifier for the group this table belongs to.

Signature:

Private Property Get SectionId() As String

Returns the section identifier for the group of tables this cross-table belongs to. A section is a logical grouping of related tables on the analysis worksheet (e.g., all time-series tables sharing the same date controls). The section ID is the table ID of the first (anchor) table in the section. It is used to reference shared named ranges like "FIRST_VALUE_START_TIME_" and "TOTAL_ROW_" that are defined once per section rather than per table.

Returns: String. The anchor table ID for this table section.


table-type #

AnalysisTableScope byte value indicating the cross-table kind.

Signature:

Private Property Get TableScope() As Byte

Returns the AnalysisTableScope byte enum value indicating what kind of cross-table this is. The table scope determines which branch of the AddFormulas Select Case is executed: ScopeGlobalSummary, ScopeUnivariate, ScopeBivariate, ScopeTimeSeries, ScopeSpatioTemporal, or ScopeSpatial. Each type has fundamentally different formula structures, named range layouts, and condition-building logic.

Returns: Byte. One of the AnalysisTableScope enum constants.

Remarks:


table-formula #

Lazy-loaded IFormulas parsing engine for this table.

Signature:

Private Property Get TableFormula() As IFormulas

Returns the IFormulas object for this table, creating it on first access via lazy initialisation. The IFormulas object (created by Formulas.Create) is the core formula-parsing engine: it knows how to translate the table summary function and variable dictionary into concrete Excel formula strings for both linelist-scope and analysis-scope contexts. Once created, the instance is cached in this.forml so subsequent calls reuse the same object without re-parsing.

Returns: IFormulas. The formula parsing engine providing ParsedLinelistFormula and ParsedAnalysisFormula methods.

Remarks:


Range and Formula Helpers

range-exists #

Check whether a named range exists on the cross-table worksheet.

Signature:

Private Function RangeExists(ByVal rngName As String) As Boolean

Utility functions for range existence checks, formula validation, string literals, percentage formulas, and conditional wrapping. Attempts to resolve a named range on the cross-table worksheet using On Error Resume Next to silently handle missing ranges. This is used before writing formulas to optional table regions (e.g., "MISSING_TOTAL_", "MISSING_MISSING_", "TOTAL_TOTAL_") that may or may not be present depending on the table missing/total configuration.

Parameters:

Returns: Boolean. True if the named range resolves to a valid Range object.


failed-formula #

Test whether a generated Excel formula string is syntactically valid.

Signature:

Private Function FailedFormula(ByVal frmName As String) As Boolean

Attempts to write the formula as an array formula into cell(1,1) of the cross-table worksheet, then immediately clears that cell. If the assignment succeeds without error, the formula is considered valid and FailedFormula returns False. If an error occurs (e.g., invalid function name, bad range reference, or formula syntax error), execution jumps to the Fail label and returns True. This acts as a safety net to prevent writing broken formulas into the actual table cells.

Parameters:

Returns: Boolean. True if the formula raised an error (is broken), False if it succeeded.

Remarks:


emp-str #

Return the Excel formula literal for an empty string.

Signature:

Private Function EmpStr() As String

Returns a two-character string consisting of two double-quote characters (Chr(34) & Chr(34)), representing the Excel formula literal for an empty string (""). This is used extensively when building condition expressions that test whether a cell is empty. Using Chr(34) avoids VBA double-quote escaping issues and keeps the formula-building code readable.

Returns: String. The literal text consisting of two Chr(34) characters.


non-emp-str #

Return the Excel formula literal for "not empty" criteria.

Signature:

Private Function NonEmpStr() As String

Returns a four-character string representing the Excel formula literal "<>" wrapped in double-quote characters (Chr(34) & "<>" & Chr(34)). This is the COUNTIFS criteria for "not empty" when used with the "N" or "N()" summary functions, which need a comparison operator inside the criteria string.

Returns: String. The literal "<>" wrapped in double-quote characters for Excel formula use.


percentage #

Build an IF(ISERR(...)) guarded percentage formula string.

Signature:

Private Function Percentage(ByVal denomRng As Range, ByVal numRng As Range, _
                            Optional ByVal rowabs As Boolean = True) As String

Builds an Excel formula string that computes a percentage (numerator / denominator) with an ISERR guard to handle division-by-zero or other errors gracefully. The generated formula follows the pattern: IF(ISERR(num/denom), "", num/denom). This ensures that when the denominator is zero or the ranges are empty, the cell displays blank instead of a #DIV/0! error. Address references use mixed absolute/relative addressing so the formula can be auto-filled down rows while keeping the denominator column fixed.

Parameters:

Returns: String. The complete IF(ISERR(...)) Excel formula text without "= " prefix.


condition #

Wrap a formula in an IF guard that blanks when a condition cell is empty.

Signature:

Private Function Condition(ByVal rngCondition As Range, ByVal formulaValue As String, _
                           Optional ByVal rowabs As Boolean = False) As String

Wraps a formula value inside an IF guard that returns an empty string when a specified condition cell is blank. The generated pattern is: IF(conditionCell = "", "", formulaValue). This is primarily used in time-series and spatio-temporal tables where formula cells should display blank when the corresponding time period label cell is empty (i.e., the row falls outside the user selected date range). Without this guard, formulas would show misleading zero/error values for unused time period rows.

Parameters:

Returns: String. The complete IF(...="","",formulaValue) Excel formula text.


Condition Management

add-table-conditions #

Append a variable-name / condition-test pair to the accumulators.

Signature:

Private Sub AddTableConditions(ByVal varName As String, ByVal conditionName As String)

Methods that maintain the parallel condVars/condTests accumulator arrays used to build multi-criteria COUNTIFS/SUMPRODUCT formulas. Appends a variable name and its corresponding condition test string to the parallel accumulator arrays (condVars and condTests). These arrays are consumed by CurrentExcelFormula to build multi-criteria COUNTIFS/SUMPRODUCT formulas. Each call adds one criteria pair. The BetterArray objects are lazily initialised on first use with a 1-based lower bound. If varName is vbNullString, nothing is pushed (silent no-op).

Parameters:

Remarks:


clear-table-conditions #

Clear or trim conditions from the accumulator arrays.

Signature:

Private Sub ClearTableConditions(Optional ByVal removeNumber As Long = 0)

Clears conditions from the condVars/condTests accumulator arrays. When called with removeNumber = 0 (the default), all accumulated conditions are cleared, resetting the formula builder to a blank state. When called with a positive removeNumber, only the last N condition pairs are popped off the arrays, leaving earlier conditions intact. This selective removal is useful when a base set of conditions is shared across multiple formulas and only the last few criteria need to change between iterations.

Parameters:

Remarks:


add-non-empty-table-conditions #

Add a "non-empty" filter condition adapted to the summary function.

Signature:

Private Sub AddNonEmptyTableConditions(ByVal varName As String)

Adds a "non-empty" filter condition for a variable, automatically adapting the condition operator to the current summary function. For count-based functions ("N" and "N()"), the condition uses the equality operator with the NonEmpStr literal ("= <>"), because COUNTIFS interprets "<>" as "not blank" inside a criteria string. For all other summary functions (SUM, MEAN, etc.), the condition uses the inequality operator with EmpStr ("<>"""), which is the standard SUMPRODUCT-style not-equal-to-empty test.

Parameters:


Excel Formula Builder

current-excel-formula #

Assembled Excel formula from the current condition accumulators.

Signature:

Private Property Get CurrentExcelFormula(Optional ByVal scope As Byte = 2, _
                                         Optional ByVal prefix As String = "f") As String

Central entry point that assembles the final Excel formula string from the accumulated condition pairs. Builds and returns the complete Excel formula string from the currently accumulated condition pairs (condVars/condTests). This is the central formula-generation entry point that all table-type branches call after setting up their conditions. It operates in two modes: (1) Linelist scope calls ParsedLinelistFormula for unfiltered formulas; (2) Analysis scope wraps the accumulated condVars and condTests into a FormulaCondition object and calls ParsedAnalysisFormula for COUNTIFS/SUMPRODUCT formulas with all criteria. After generation, the formula is validated via FailedFormula, returning a fallback error placeholder if parsing failed.

Parameters:

Returns: String. The Excel formula text without "= " prefix, or an error placeholder if parsing failed.

Remarks:


Number Format Helper

add-rng-format #

Apply an Excel NumberFormat to a range based on a format keyword.

Signature:

Private Sub AddRngFormat(ByVal formatValue As String, ByVal rng As Range)

Translates human-readable format keywords into Excel NumberFormat strings. Translates configuration-friendly format keywords into proper Excel custom number format strings and applies them to the target range. Supported keywords include "integer", "round0", "roundN", "percentage0", "percentageN", "euros", "dollars", and "text". Any unrecognised string is treated as a custom Excel NumberFormat and applied directly inside On Error Resume Next. If formatValue is vbNullString, the sub exits without modifying the range.

Parameters:

Remarks:


Error Handling

throw-error #

Raise a project error with a standardised source name.

Signature:

Private Sub ThrowError(ByVal errNumber As Long, ByVal message As String)

Standardised error-raising helper for all methods in this class. Wrapper around Err.Raise that standardises the source to CLASS_NAME, providing a consistent stack trace across all methods in this class.

Parameters:

Throws:


Interface Implementation

ICrossTableFormula_Valid #

Signature:

Private Property Get ICrossTableFormula_Valid() As Boolean

Delegated members satisfying the ICrossTableFormula contract. See the corresponding Public/Private members above for full documentation.


Used in (4 file(s))