Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packages/o-spreadsheet-engine/src/functions/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ export function toNumber(
}
}

/** Convert the data to a number, ignoring undefined values */
export function toNumberIgnoreUndefined(
data: FunctionResultObject | CellValue | undefined,
locale: Locale
): number | undefined {
return data === undefined ? undefined : toNumber(data, locale);
}

export function tryToNumber(
value: string | number | boolean | null | undefined,
locale: Locale
Expand Down Expand Up @@ -220,6 +228,13 @@ export function toBoolean(data: FunctionResultObject | CellValue | undefined): b
}
}

/** Convert the data to a boolean, ignoring undefined values */
export function toBooleanIgnoreUndefined(
data: FunctionResultObject | CellValue | undefined
): boolean | undefined {
return data === undefined ? undefined : toBoolean(data);
}

function strictToBoolean(data: FunctionResultObject | CellValue | undefined): boolean {
const value = toValue(data);
if (value === "") {
Expand Down
58 changes: 32 additions & 26 deletions packages/o-spreadsheet-engine/src/functions/module_lookup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { PIVOT_MAX_NUMBER_OF_CELLS } from "../constants";
import { getFullReference, splitReference } from "../helpers/";
import { toXC } from "../helpers/coordinates";
import { range } from "../helpers/misc";
import { addAlignFormatToPivotHeader } from "../helpers/pivot/pivot_helpers";
import {
addAlignFormatToPivotHeader,
getPivotStyleFromFnArgs,
} from "../helpers/pivot/pivot_helpers";
import { toZone } from "../helpers/zones";
import { _t } from "../translation";
import { CellErrorType, EvaluationError, InvalidReferenceError } from "../types/errors";
import { AddFunctionDescription } from "../types/functions";
import { Arg, FunctionResultObject, Matrix, Maybe, Zone } from "../types/misc";
import { PivotVisibilityOptions } from "../types/pivot";
import { arg } from "./arguments";
import { expectNumberGreaterThanOrEqualToOne } from "./helper_assert";
import {
Expand All @@ -19,12 +21,12 @@ import {
getPivotId,
} from "./helper_lookup";
import {
LinearSearchMode,
dichotomicSearch,
expectNumberRangeError,
generateMatrix,
isEvaluationError,
linearSearch,
LinearSearchMode,
strictToInteger,
toBoolean,
toMatrix,
Expand Down Expand Up @@ -907,30 +909,34 @@ export const PIVOT = {
],
compute: function (
pivotFormulaId: Maybe<FunctionResultObject>,
rowCount: Maybe<FunctionResultObject> = { value: 10000 },
includeTotal: Maybe<FunctionResultObject> = { value: true },
includeColumnHeaders: Maybe<FunctionResultObject> = { value: true },
columnCount: Maybe<FunctionResultObject> = { value: Number.MAX_VALUE },
includeMeasureTitles: Maybe<FunctionResultObject> = { value: true }
rowCount: Maybe<FunctionResultObject>,
includeTotal: Maybe<FunctionResultObject>,
includeColumnHeaders: Maybe<FunctionResultObject>,
columnCount: Maybe<FunctionResultObject>,
includeMeasureTitles: Maybe<FunctionResultObject>
) {
const _pivotFormulaId = toString(pivotFormulaId);
const _rowCount = toNumber(rowCount, this.locale);
if (_rowCount < 0) {
const pivotId = getPivotId(_pivotFormulaId, this.getters);
const pivot = this.getters.getPivot(pivotId);
const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);

const pivotStyle = getPivotStyleFromFnArgs(
coreDefinition,
rowCount,
includeTotal,
includeColumnHeaders,
columnCount,
includeMeasureTitles,
this.locale
);

if (pivotStyle.numberOfRows < 0) {
return new EvaluationError(_t("The number of rows must be positive."));
}
const _columnCount = toNumber(columnCount, this.locale);
if (_columnCount < 0) {
if (pivotStyle.numberOfColumns < 0) {
return new EvaluationError(_t("The number of columns must be positive."));
}
const visibilityOptions: PivotVisibilityOptions = {
displayColumnHeaders: toBoolean(includeColumnHeaders),
displayTotals: toBoolean(includeTotal),
displayMeasuresRow: toBoolean(includeMeasureTitles),
};

const pivotId = getPivotId(_pivotFormulaId, this.getters);
const pivot = this.getters.getPivot(pivotId);
const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
addPivotDependencies(this, coreDefinition, coreDefinition.measures);
pivot.init({ reload: pivot.needsReevaluation });
const error = pivot.assertIsValid({ throwOnError: false });
Expand All @@ -941,21 +947,21 @@ export const PIVOT = {
if (table.numberOfCells > PIVOT_MAX_NUMBER_OF_CELLS) {
return new EvaluationError(getPivotTooBigErrorMessage(table.numberOfCells, this.locale));
}
const cells = table.getPivotCells(visibilityOptions);
const cells = table.getPivotCells(pivotStyle);

let headerRows = 0;
if (visibilityOptions.displayColumnHeaders) {
if (pivotStyle.displayColumnHeaders) {
headerRows = table.columns.length - 1;
}
if (visibilityOptions.displayMeasuresRow) {
if (pivotStyle.displayMeasuresRow) {
headerRows++;
}
const pivotTitle = this.getters.getPivotName(pivotId);
const tableHeight = Math.min(headerRows + _rowCount, cells[0].length);
const tableHeight = Math.min(headerRows + pivotStyle.numberOfRows, cells[0].length);
if (tableHeight === 0) {
return [[{ value: pivotTitle }]];
}
const tableWidth = Math.min(1 + _columnCount, cells.length);
const tableWidth = Math.min(1 + pivotStyle.numberOfColumns, cells.length);
const result: Matrix<FunctionResultObject> = [];
for (const col of range(0, tableWidth)) {
result[col] = [];
Expand All @@ -978,7 +984,7 @@ export const PIVOT = {
}
}
}
if (visibilityOptions.displayColumnHeaders || visibilityOptions.displayMeasuresRow) {
if (pivotStyle.displayColumnHeaders || pivotStyle.displayMeasuresRow) {
result[0][0] = { value: pivotTitle };
}
return result;
Expand Down
45 changes: 45 additions & 0 deletions packages/o-spreadsheet-engine/src/helpers/pivot/pivot_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
PivotField,
PivotFields,
PivotSortedColumn,
PivotStyle,
PivotTableCell,
} from "../../types/pivot";
import { Pivot } from "../../types/pivot_runtime";
Expand All @@ -33,6 +34,14 @@ import { deepEquals, getUniqueText, isDefined } from "../misc";
import { PivotRuntimeDefinition } from "./pivot_runtime_definition";
import { pivotTimeAdapter } from "./pivot_time_adapter";

export const DEFAULT_PIVOT_STYLE: Required<PivotStyle> = {
displayTotals: true,
displayColumnHeaders: true,
displayMeasuresRow: true,
numberOfRows: Number.MAX_VALUE,
numberOfColumns: Number.MAX_VALUE,
};

const AGGREGATOR_NAMES = {
count: _t("Count"),
count_distinct: _t("Count Distinct"),
Expand Down Expand Up @@ -446,3 +455,39 @@ export function togglePivotCollapse(position: CellPosition, env: SpreadsheetChil
pivot: { ...definition, collapsedDomains: newDomains },
});
}

export function getPivotStyleFromFnArgs(
definition: PivotCoreDefinition,
rowCountArg: Maybe<FunctionResultObject | CellValue>,
includeTotalArg: Maybe<FunctionResultObject | CellValue>,
includeColumnHeadersArg: Maybe<FunctionResultObject | CellValue>,
columnCountArg: Maybe<FunctionResultObject | CellValue>,
includeMeasuresRowArg: Maybe<FunctionResultObject | CellValue>,
locale: Locale
): Required<PivotStyle> {
const style = definition.style;

const numberOfRows =
rowCountArg !== undefined
? toNumber(rowCountArg, locale)
: style?.numberOfRows ?? DEFAULT_PIVOT_STYLE.numberOfRows;
const numberOfColumns =
columnCountArg !== undefined
? toNumber(columnCountArg, locale)
: style?.numberOfColumns ?? DEFAULT_PIVOT_STYLE.numberOfColumns;

const displayTotals =
includeTotalArg !== undefined
? toBoolean(includeTotalArg)
: style?.displayTotals ?? DEFAULT_PIVOT_STYLE.displayTotals;
const displayColumnHeaders =
includeColumnHeadersArg !== undefined
? toBoolean(includeColumnHeadersArg)
: style?.displayColumnHeaders ?? DEFAULT_PIVOT_STYLE.displayColumnHeaders;
const displayMeasuresRow =
includeMeasuresRowArg !== undefined
? toBoolean(includeMeasuresRowArg)
: style?.displayMeasuresRow ?? DEFAULT_PIVOT_STYLE.displayMeasuresRow;

return { numberOfRows, numberOfColumns, displayTotals, displayColumnHeaders, displayMeasuresRow };
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import {
PivotCollapsedDomains,
PivotDomain,
PivotSortedColumn,
PivotStyle,
PivotTableCell,
PivotTableColumn,
PivotTableRow,
PivotVisibilityOptions,
} from "../../types/pivot";
import { deepEquals, lazy } from "../misc";
import { isParentDomain, sortPivotTree } from "./pivot_domain_helpers";
import { parseDimension, toNormalizedPivotValue } from "./pivot_helpers";
import { DEFAULT_PIVOT_STYLE, parseDimension, toNormalizedPivotValue } from "./pivot_helpers";

interface CollapsiblePivotTableColumn extends PivotTableColumn {
collapsedHeader?: boolean;
Expand Down Expand Up @@ -157,29 +157,23 @@ export class SpreadsheetPivotTable {
return this.columns.at(-1)?.length || 0;
}

private getSkippedRows(visibilityOptions: PivotVisibilityOptions) {
private getSkippedRows(pivotStyle: Required<PivotStyle>) {
const skippedRows: Set<number> = new Set();
if (!visibilityOptions.displayColumnHeaders) {
if (!pivotStyle.displayColumnHeaders) {
for (let i = 0; i < this.columns.length - 1; i++) {
skippedRows.add(i);
}
}
if (!visibilityOptions.displayMeasuresRow) {
if (!pivotStyle.displayMeasuresRow) {
skippedRows.add(this.columns.length - 1);
}
return skippedRows;
}

getPivotCells(
visibilityOptions: PivotVisibilityOptions = {
displayColumnHeaders: true,
displayTotals: true,
displayMeasuresRow: true,
}
): PivotTableCell[][] {
const key = JSON.stringify(visibilityOptions);
getPivotCells(pivotStyle: Required<PivotStyle> = DEFAULT_PIVOT_STYLE): PivotTableCell[][] {
const key = JSON.stringify(pivotStyle);
if (!this.pivotCells[key]) {
const { displayTotals } = visibilityOptions;
const { displayTotals } = pivotStyle;
const numberOfDataRows = this.rows.length;
const numberOfDataColumns = this.getNumberOfDataColumns();
let pivotHeight = this.columns.length + numberOfDataRows;
Expand All @@ -191,7 +185,7 @@ export class SpreadsheetPivotTable {
pivotWidth -= this.measures.length;
}
const domainArray: PivotTableCell[][] = [];
const skippedRows = this.getSkippedRows(visibilityOptions);
const skippedRows = this.getSkippedRows(pivotStyle);
for (let col = 0; col < pivotWidth; col++) {
domainArray.push([]);
for (let row = 0; row < pivotHeight; row++) {
Expand Down
28 changes: 12 additions & 16 deletions packages/o-spreadsheet-engine/src/plugins/ui_core_views/pivot_ui.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { astToFormula } from "../../formulas/formula_formatter";
import { Token } from "../../formulas/tokenizer";
import { toScalar } from "../../functions/helper_matrices";
import { toBoolean } from "../../functions/helpers";
import { deepEquals, getUniqueText } from "../../helpers/misc";
import {
getFirstPivotFunction,
getNumberOfPivotFunctions,
} from "../../helpers/pivot/pivot_composer_helpers";
import { domainToColRowDomain } from "../../helpers/pivot/pivot_domain_helpers";
import { getPivotStyleFromFnArgs } from "../../helpers/pivot/pivot_helpers";
import withPivotPresentationLayer from "../../helpers/pivot/pivot_presentation";
import { pivotRegistry } from "../../helpers/pivot/pivot_registry";
import { resetMapValueDimensionDate } from "../../helpers/pivot/spreadsheet_pivot/date_spreadsheet_pivot";
Expand All @@ -21,7 +21,7 @@ import {
UpdatePivotCommand,
} from "../../types/commands";
import { CellPosition, FunctionResultObject, isMatrix, SortDirection, UID } from "../../types/misc";
import { PivotCoreMeasure, PivotTableCell, PivotVisibilityOptions } from "../../types/pivot";
import { PivotCoreMeasure, PivotTableCell } from "../../types/pivot";
import { Pivot } from "../../types/pivot_runtime";
import { CoreViewPlugin, CoreViewPluginConfig } from "../core_view_plugin";
import { UIPluginConfig } from "../ui_plugin";
Expand Down Expand Up @@ -219,20 +219,16 @@ export class PivotUIPlugin extends CoreViewPlugin {
return EMPTY_PIVOT_CELL;
}
if (functionName === "PIVOT") {
const includeTotal = toScalar(args[2]);
const shouldIncludeTotal = includeTotal === undefined ? true : toBoolean(includeTotal);
const includeColumnHeaders = toScalar(args[3]);
const includeMeasures = toScalar(args[5]);
const shouldIncludeMeasures =
includeMeasures === undefined ? true : toBoolean(includeMeasures);
const shouldIncludeColumnHeaders =
includeColumnHeaders === undefined ? true : toBoolean(includeColumnHeaders);
const visibilityOptions: PivotVisibilityOptions = {
displayColumnHeaders: shouldIncludeColumnHeaders,
displayTotals: shouldIncludeTotal,
displayMeasuresRow: shouldIncludeMeasures,
};
const pivotCells = pivot.getCollapsedTableStructure().getPivotCells(visibilityOptions);
const pivotStyle = getPivotStyleFromFnArgs(
this.getters.getPivotCoreDefinition(pivotId),
toScalar(args[1]),
toScalar(args[2]),
toScalar(args[3]),
toScalar(args[4]),
toScalar(args[5]),
this.getters.getLocale()
);
const pivotCells = pivot.getCollapsedTableStructure().getPivotCells(pivotStyle);
const pivotCol = position.col - mainPosition.col;
const pivotRow = position.row - mainPosition.row;
return pivotCells[pivotCol][pivotRow];
Expand Down
11 changes: 7 additions & 4 deletions packages/o-spreadsheet-engine/src/types/pivot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export interface CommonPivotCoreDefinition {
sortedColumn?: PivotSortedColumn;
collapsedDomains?: PivotCollapsedDomains;
customFields?: Record<string, PivotCustomGroupedField>;
style?: PivotStyle;
}

export interface PivotSortedColumn {
Expand Down Expand Up @@ -239,8 +240,10 @@ export interface DimensionTreeNode {

export type DimensionTree = DimensionTreeNode[];

export interface PivotVisibilityOptions {
displayColumnHeaders: boolean;
displayTotals: boolean;
displayMeasuresRow: boolean;
export interface PivotStyle {
numberOfRows?: number;
numberOfColumns?: number;
displayTotals?: boolean;
displayColumnHeaders?: boolean;
displayMeasuresRow?: boolean;
}
Comment on lines +243 to 249
Copy link
Contributor Author

@hokolomopo hokolomopo Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having everything optional makes it a bit harder to use, we need to do style.displayTotals ?? DEFAULT_PIVOT_STYLE.displayTotals

  1. It's fine
  2. rename variables so they are falsy by default (eg. hideTotals instead)
  3. make everything mandatory. Need migration + make json/revisions a bit more heavy

note that 2/3 don't solve everything, it only works for boolean values not really for number values (I don't think we want to add Number.MAX_VALUE or w/e in the JSON)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would go for 1, it's fine, especially because as you say 2 and 3 are not perfect :)

This file was deleted.

Loading