@odoo/o-spreadsheet 17.4.0-alpha.5 → 17.4.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/o-spreadsheet.cjs.js +1234 -747
- package/dist/o-spreadsheet.d.ts +232 -120
- package/dist/o-spreadsheet.esm.js +1234 -747
- package/dist/o-spreadsheet.iife.js +1234 -747
- package/dist/o-spreadsheet.iife.min.js +377 -344
- package/dist/o_spreadsheet.xml +250 -23
- package/package.json +6 -4
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
|
|
2
|
-
// @odoo-module ignore
|
|
3
2
|
/**
|
|
4
3
|
* This file is generated by o-spreadsheet build tools. Do not edit it.
|
|
5
4
|
* @see https://github.com/odoo/o-spreadsheet
|
|
6
|
-
* @version 17.4.0-alpha.
|
|
7
|
-
* @date 2024-06-
|
|
8
|
-
* @hash
|
|
5
|
+
* @version 17.4.0-alpha.7
|
|
6
|
+
* @date 2024-06-21T08:42:22.370Z
|
|
7
|
+
* @hash a99db9a
|
|
9
8
|
*/
|
|
10
9
|
|
|
11
10
|
import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
|
|
@@ -6200,7 +6199,7 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6200
6199
|
};
|
|
6201
6200
|
}
|
|
6202
6201
|
isPasteAllowed(sheetId, target, content, clipboardOptions) {
|
|
6203
|
-
if (!
|
|
6202
|
+
if (!content.cells) {
|
|
6204
6203
|
return "Success" /* CommandResult.Success */;
|
|
6205
6204
|
}
|
|
6206
6205
|
if (clipboardOptions?.isCutOperation && clipboardOptions?.pasteOption !== undefined) {
|
|
@@ -6214,6 +6213,17 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6214
6213
|
return "WrongPasteSelection" /* CommandResult.WrongPasteSelection */;
|
|
6215
6214
|
}
|
|
6216
6215
|
}
|
|
6216
|
+
const clipboardHeight = content.cells.length;
|
|
6217
|
+
const clipboardWidth = content.cells[0].length;
|
|
6218
|
+
for (const zone of getPasteZones(target, content.cells)) {
|
|
6219
|
+
if (this.getters.doesIntersectMerge(sheetId, zone)) {
|
|
6220
|
+
if (target.length > 1 ||
|
|
6221
|
+
!this.getters.isSingleCellOrMerge(sheetId, target[0]) ||
|
|
6222
|
+
clipboardHeight * clipboardWidth !== 1) {
|
|
6223
|
+
return "WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */;
|
|
6224
|
+
}
|
|
6225
|
+
}
|
|
6226
|
+
}
|
|
6217
6227
|
return "Success" /* CommandResult.Success */;
|
|
6218
6228
|
}
|
|
6219
6229
|
/**
|
|
@@ -6453,13 +6463,22 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6453
6463
|
}
|
|
6454
6464
|
const { rowsIndexes, columnsIndexes } = data;
|
|
6455
6465
|
const sheetId = data.sheetId;
|
|
6456
|
-
const
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6466
|
+
const cfRules = [];
|
|
6467
|
+
for (const row of rowsIndexes) {
|
|
6468
|
+
const cfRuleInRow = [];
|
|
6469
|
+
for (const col of columnsIndexes) {
|
|
6470
|
+
const cfRules = Array.from(this.getters.getRulesByCell(sheetId, col, row));
|
|
6471
|
+
cfRuleInRow.push({
|
|
6472
|
+
position: { col, row, sheetId },
|
|
6473
|
+
rules: cfRules,
|
|
6474
|
+
});
|
|
6475
|
+
}
|
|
6476
|
+
cfRules.push(cfRuleInRow);
|
|
6477
|
+
}
|
|
6478
|
+
return { cfRules };
|
|
6460
6479
|
}
|
|
6461
6480
|
paste(target, clippedContent, options) {
|
|
6462
|
-
if (!clippedContent?.
|
|
6481
|
+
if (!clippedContent?.cfRules ||
|
|
6463
6482
|
options?.pasteOption === "asValue" ||
|
|
6464
6483
|
!("zones" in target) ||
|
|
6465
6484
|
!target.zones.length) {
|
|
@@ -6468,7 +6487,7 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6468
6487
|
const zones = target.zones;
|
|
6469
6488
|
const sheetId = target.sheetId;
|
|
6470
6489
|
if (!options?.isCutOperation) {
|
|
6471
|
-
this.pasteFromCopy(sheetId, zones, clippedContent.
|
|
6490
|
+
this.pasteFromCopy(sheetId, zones, clippedContent.cfRules, options);
|
|
6472
6491
|
}
|
|
6473
6492
|
else {
|
|
6474
6493
|
this.pasteFromCut(sheetId, zones, clippedContent);
|
|
@@ -6476,12 +6495,12 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6476
6495
|
}
|
|
6477
6496
|
pasteFromCut(sheetId, target, content) {
|
|
6478
6497
|
const selection = target[0];
|
|
6479
|
-
this.pasteZone(sheetId, selection.left, selection.top, content.
|
|
6498
|
+
this.pasteZone(sheetId, selection.left, selection.top, content.cfRules, {
|
|
6480
6499
|
isCutOperation: true,
|
|
6481
6500
|
});
|
|
6482
6501
|
}
|
|
6483
|
-
pasteZone(sheetId, col, row,
|
|
6484
|
-
for (const [r, rowCells] of
|
|
6502
|
+
pasteZone(sheetId, col, row, cfRules, clipboardOptions) {
|
|
6503
|
+
for (const [r, rowCells] of cfRules.entries()) {
|
|
6485
6504
|
for (const [c, origin] of rowCells.entries()) {
|
|
6486
6505
|
const position = { col: col + c, row: row + r, sheetId };
|
|
6487
6506
|
this.pasteCf(origin, position, clipboardOptions?.isCutOperation);
|
|
@@ -6489,23 +6508,21 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6489
6508
|
}
|
|
6490
6509
|
}
|
|
6491
6510
|
pasteCf(origin, target, isCutOperation) {
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
for (const
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
this.adaptCFRules(target.sheetId, cfToCopyTo, [zone], []);
|
|
6508
|
-
}
|
|
6511
|
+
if (origin?.rules && origin.rules.length > 0) {
|
|
6512
|
+
const zone = positionToZone(target);
|
|
6513
|
+
for (const rule of origin.rules) {
|
|
6514
|
+
const toRemoveZones = [];
|
|
6515
|
+
if (isCutOperation) {
|
|
6516
|
+
//remove from current rule
|
|
6517
|
+
toRemoveZones.push(positionToZone(origin.position));
|
|
6518
|
+
}
|
|
6519
|
+
if (origin.position.sheetId === target.sheetId) {
|
|
6520
|
+
this.adaptCFRules(origin.position.sheetId, rule, [zone], toRemoveZones);
|
|
6521
|
+
}
|
|
6522
|
+
else {
|
|
6523
|
+
this.adaptCFRules(origin.position.sheetId, rule, [], toRemoveZones);
|
|
6524
|
+
const cfToCopyTo = this.getCFToCopyTo(target.sheetId, rule);
|
|
6525
|
+
this.adaptCFRules(target.sheetId, cfToCopyTo, [zone], []);
|
|
6509
6526
|
}
|
|
6510
6527
|
}
|
|
6511
6528
|
}
|
|
@@ -6548,13 +6565,20 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6548
6565
|
}
|
|
6549
6566
|
const { rowsIndexes, columnsIndexes } = data;
|
|
6550
6567
|
const sheetId = data.sheetId;
|
|
6551
|
-
const
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6568
|
+
const dvRules = [];
|
|
6569
|
+
for (const row of rowsIndexes) {
|
|
6570
|
+
const dvRuleInRow = [];
|
|
6571
|
+
for (const col of columnsIndexes) {
|
|
6572
|
+
const position = { sheetId, col, row };
|
|
6573
|
+
const rule = this.getters.getValidationRuleForCell(position);
|
|
6574
|
+
dvRuleInRow.push({ position, rule });
|
|
6575
|
+
}
|
|
6576
|
+
dvRules.push(dvRuleInRow);
|
|
6577
|
+
}
|
|
6578
|
+
return { dvRules };
|
|
6555
6579
|
}
|
|
6556
6580
|
paste(target, clippedContent, options) {
|
|
6557
|
-
if (!clippedContent?.
|
|
6581
|
+
if (!clippedContent?.dvRules) {
|
|
6558
6582
|
return;
|
|
6559
6583
|
}
|
|
6560
6584
|
if (options?.pasteOption) {
|
|
@@ -6566,7 +6590,7 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6566
6590
|
const zones = target.zones;
|
|
6567
6591
|
const sheetId = target.sheetId;
|
|
6568
6592
|
if (!options?.isCutOperation) {
|
|
6569
|
-
this.pasteFromCopy(sheetId, zones, clippedContent.
|
|
6593
|
+
this.pasteFromCopy(sheetId, zones, clippedContent.dvRules);
|
|
6570
6594
|
}
|
|
6571
6595
|
else {
|
|
6572
6596
|
this.pasteFromCut(sheetId, zones, clippedContent);
|
|
@@ -6574,12 +6598,12 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6574
6598
|
}
|
|
6575
6599
|
pasteFromCut(sheetId, target, content) {
|
|
6576
6600
|
const selection = target[0];
|
|
6577
|
-
this.pasteZone(sheetId, selection.left, selection.top, content.
|
|
6601
|
+
this.pasteZone(sheetId, selection.left, selection.top, content.dvRules, {
|
|
6578
6602
|
isCutOperation: true,
|
|
6579
6603
|
});
|
|
6580
6604
|
}
|
|
6581
|
-
pasteZone(sheetId, col, row,
|
|
6582
|
-
for (const [r, rowCells] of
|
|
6605
|
+
pasteZone(sheetId, col, row, dvRules, clipboardOptions) {
|
|
6606
|
+
for (const [r, rowCells] of dvRules.entries()) {
|
|
6583
6607
|
for (const [c, origin] of rowCells.entries()) {
|
|
6584
6608
|
const position = { col: col + c, row: row + r, sheetId };
|
|
6585
6609
|
this.pasteDataValidation(origin, position, clipboardOptions?.isCutOperation);
|
|
@@ -6587,41 +6611,43 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
6587
6611
|
}
|
|
6588
6612
|
}
|
|
6589
6613
|
pasteDataValidation(origin, target, isCutOperation) {
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
const
|
|
6593
|
-
if (
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
}
|
|
6599
|
-
const zone = positionToZone(target);
|
|
6600
|
-
for (const range of rule.ranges) {
|
|
6601
|
-
if (isInside(origin.col, origin.row, range.zone)) {
|
|
6602
|
-
const toRemoveZone = [];
|
|
6603
|
-
if (isCutOperation) {
|
|
6604
|
-
toRemoveZone.push(positionToZone(origin));
|
|
6605
|
-
}
|
|
6606
|
-
if (origin.sheetId === target.sheetId) {
|
|
6607
|
-
this.adaptDataValidationRule(origin.sheetId, rule, [zone], toRemoveZone);
|
|
6614
|
+
if (origin) {
|
|
6615
|
+
const zone = positionToZone(target);
|
|
6616
|
+
const rule = origin.rule;
|
|
6617
|
+
if (!rule) {
|
|
6618
|
+
const targetRule = this.getters.getValidationRuleForCell(target);
|
|
6619
|
+
if (targetRule) {
|
|
6620
|
+
// Remove the data validation rule on the target cell
|
|
6621
|
+
this.adaptDataValidationRule(target.sheetId, targetRule, [], [zone]);
|
|
6608
6622
|
}
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
6623
|
+
return;
|
|
6624
|
+
}
|
|
6625
|
+
const toRemoveZone = [];
|
|
6626
|
+
if (isCutOperation) {
|
|
6627
|
+
toRemoveZone.push(positionToZone(origin.position));
|
|
6628
|
+
}
|
|
6629
|
+
if (origin.position.sheetId === target.sheetId) {
|
|
6630
|
+
const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule, false);
|
|
6631
|
+
this.adaptDataValidationRule(origin.position.sheetId, copyToRule, [zone], toRemoveZone);
|
|
6632
|
+
}
|
|
6633
|
+
else {
|
|
6634
|
+
const originRule = this.getters.getValidationRuleForCell(origin.position);
|
|
6635
|
+
if (originRule) {
|
|
6636
|
+
this.adaptDataValidationRule(origin.position.sheetId, originRule, [], toRemoveZone);
|
|
6613
6637
|
}
|
|
6638
|
+
const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule);
|
|
6639
|
+
this.adaptDataValidationRule(target.sheetId, copyToRule, [zone], []);
|
|
6614
6640
|
}
|
|
6615
6641
|
}
|
|
6616
6642
|
}
|
|
6617
|
-
getDataValidationRuleToCopyTo(targetSheetId, originRule) {
|
|
6643
|
+
getDataValidationRuleToCopyTo(targetSheetId, originRule, newId = true) {
|
|
6618
6644
|
const ruleInTargetSheet = this.getters
|
|
6619
6645
|
.getDataValidationRules(targetSheetId)
|
|
6620
6646
|
.find((rule) => deepEquals(originRule.criterion, rule.criterion) &&
|
|
6621
6647
|
originRule.isBlocking === rule.isBlocking);
|
|
6622
6648
|
return ruleInTargetSheet
|
|
6623
6649
|
? ruleInTargetSheet
|
|
6624
|
-
: { ...originRule, id: this.uuidGenerator.uuidv4(), ranges: [] };
|
|
6650
|
+
: { ...originRule, id: newId ? this.uuidGenerator.uuidv4() : originRule.id, ranges: [] };
|
|
6625
6651
|
}
|
|
6626
6652
|
/**
|
|
6627
6653
|
* Add or remove XCs to a given data validation rule.
|
|
@@ -6713,69 +6739,63 @@ class ImageClipboardHandler extends AbstractFigureClipboardHandler {
|
|
|
6713
6739
|
}
|
|
6714
6740
|
|
|
6715
6741
|
class MergeClipboardHandler extends AbstractCellClipboardHandler {
|
|
6716
|
-
|
|
6717
|
-
if (!
|
|
6718
|
-
return
|
|
6742
|
+
copy(data) {
|
|
6743
|
+
if (!data.zones.length) {
|
|
6744
|
+
return;
|
|
6719
6745
|
}
|
|
6720
|
-
const
|
|
6721
|
-
const
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
}
|
|
6746
|
+
const sheetId = this.getters.getActiveSheetId();
|
|
6747
|
+
const { rowsIndexes, columnsIndexes } = data;
|
|
6748
|
+
const merges = [];
|
|
6749
|
+
for (const row of rowsIndexes) {
|
|
6750
|
+
const mergesInRow = [];
|
|
6751
|
+
for (const col of columnsIndexes) {
|
|
6752
|
+
const position = { col, row, sheetId };
|
|
6753
|
+
mergesInRow.push(this.getters.getMerge(position));
|
|
6729
6754
|
}
|
|
6755
|
+
merges.push(mergesInRow);
|
|
6730
6756
|
}
|
|
6731
|
-
return
|
|
6757
|
+
return { merges };
|
|
6732
6758
|
}
|
|
6733
6759
|
/**
|
|
6734
6760
|
* Paste the clipboard content in the given target
|
|
6735
6761
|
*/
|
|
6736
6762
|
paste(target, content, options) {
|
|
6737
|
-
if (
|
|
6763
|
+
if (!content.merges ||
|
|
6764
|
+
options?.isCutOperation ||
|
|
6765
|
+
!("zones" in target) ||
|
|
6766
|
+
!target.zones.length) {
|
|
6738
6767
|
return;
|
|
6739
6768
|
}
|
|
6740
|
-
this.pasteFromCopy(target.sheetId, target.zones, content.
|
|
6769
|
+
this.pasteFromCopy(target.sheetId, target.zones, content.merges, options);
|
|
6741
6770
|
}
|
|
6742
|
-
pasteZone(sheetId, col, row,
|
|
6743
|
-
for (const [r,
|
|
6744
|
-
for (const [c,
|
|
6745
|
-
if (!origin.position) {
|
|
6746
|
-
continue;
|
|
6747
|
-
}
|
|
6771
|
+
pasteZone(sheetId, col, row, merges) {
|
|
6772
|
+
for (const [r, rowMerges] of merges.entries()) {
|
|
6773
|
+
for (const [c, originMerge] of rowMerges.entries()) {
|
|
6748
6774
|
const position = { col: col + c, row: row + r, sheetId };
|
|
6749
|
-
this.
|
|
6775
|
+
this.pasteMerge(originMerge, position);
|
|
6750
6776
|
}
|
|
6751
6777
|
}
|
|
6752
6778
|
}
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
const { col: mainCellColOrigin, row: mainCellRowOrigin } = this.getters.getMainCellPosition(origin);
|
|
6760
|
-
if (mainCellColOrigin === col && mainCellRowOrigin === row) {
|
|
6761
|
-
const merge = this.getters.getMerge(origin);
|
|
6762
|
-
if (!merge) {
|
|
6763
|
-
return;
|
|
6764
|
-
}
|
|
6765
|
-
({ sheetId, col, row } = target);
|
|
6766
|
-
this.dispatch("ADD_MERGE", {
|
|
6767
|
-
sheetId,
|
|
6768
|
-
force: true,
|
|
6769
|
-
target: [
|
|
6770
|
-
{
|
|
6771
|
-
left: col,
|
|
6772
|
-
top: row,
|
|
6773
|
-
right: col + merge.right - merge.left,
|
|
6774
|
-
bottom: row + merge.bottom - merge.top,
|
|
6775
|
-
},
|
|
6776
|
-
],
|
|
6777
|
-
});
|
|
6779
|
+
pasteMerge(originMerge, target) {
|
|
6780
|
+
if (!originMerge) {
|
|
6781
|
+
return;
|
|
6782
|
+
}
|
|
6783
|
+
if (this.getters.isInMerge(target)) {
|
|
6784
|
+
return;
|
|
6778
6785
|
}
|
|
6786
|
+
const { sheetId, col, row } = target;
|
|
6787
|
+
this.dispatch("ADD_MERGE", {
|
|
6788
|
+
sheetId,
|
|
6789
|
+
force: true,
|
|
6790
|
+
target: [
|
|
6791
|
+
{
|
|
6792
|
+
left: col,
|
|
6793
|
+
top: row,
|
|
6794
|
+
right: col + originMerge.right - originMerge.left,
|
|
6795
|
+
bottom: row + originMerge.bottom - originMerge.top,
|
|
6796
|
+
},
|
|
6797
|
+
],
|
|
6798
|
+
});
|
|
6779
6799
|
}
|
|
6780
6800
|
}
|
|
6781
6801
|
|
|
@@ -7755,6 +7775,486 @@ function errorCell(value, message) {
|
|
|
7755
7775
|
};
|
|
7756
7776
|
}
|
|
7757
7777
|
|
|
7778
|
+
function boolAnd(args) {
|
|
7779
|
+
let foundBoolean = false;
|
|
7780
|
+
let acc = true;
|
|
7781
|
+
conditionalVisitBoolean(args, (arg) => {
|
|
7782
|
+
foundBoolean = true;
|
|
7783
|
+
acc = acc && arg;
|
|
7784
|
+
return acc;
|
|
7785
|
+
});
|
|
7786
|
+
return {
|
|
7787
|
+
foundBoolean,
|
|
7788
|
+
result: acc,
|
|
7789
|
+
};
|
|
7790
|
+
}
|
|
7791
|
+
function boolOr(args) {
|
|
7792
|
+
let foundBoolean = false;
|
|
7793
|
+
let acc = false;
|
|
7794
|
+
conditionalVisitBoolean(args, (arg) => {
|
|
7795
|
+
foundBoolean = true;
|
|
7796
|
+
acc = acc || arg;
|
|
7797
|
+
return !acc;
|
|
7798
|
+
});
|
|
7799
|
+
return {
|
|
7800
|
+
foundBoolean,
|
|
7801
|
+
result: acc,
|
|
7802
|
+
};
|
|
7803
|
+
}
|
|
7804
|
+
|
|
7805
|
+
function sum(values, locale) {
|
|
7806
|
+
return reduceNumbers(values, (acc, a) => acc + a, 0, locale);
|
|
7807
|
+
}
|
|
7808
|
+
function countUnique(args) {
|
|
7809
|
+
return reduceAny(args, (acc, a) => (isDataNonEmpty(a) ? acc.add(a?.value) : acc), new Set()).size;
|
|
7810
|
+
}
|
|
7811
|
+
|
|
7812
|
+
function assertSameNumberOfElements(...args) {
|
|
7813
|
+
const dims = args[0].length;
|
|
7814
|
+
args.forEach((arg, i) => assert(() => arg.length === dims, _t("[[FUNCTION_NAME]] has mismatched dimensions for argument %s (%s vs %s).", i.toString(), dims.toString(), arg.length.toString())));
|
|
7815
|
+
}
|
|
7816
|
+
function average(values, locale) {
|
|
7817
|
+
let count = 0;
|
|
7818
|
+
const sum = reduceNumbers(values, (acc, a) => {
|
|
7819
|
+
count += 1;
|
|
7820
|
+
return acc + a;
|
|
7821
|
+
}, 0, locale);
|
|
7822
|
+
assertNotZero(count);
|
|
7823
|
+
return sum / count;
|
|
7824
|
+
}
|
|
7825
|
+
function countNumbers(values, locale) {
|
|
7826
|
+
let count = 0;
|
|
7827
|
+
for (let n of values) {
|
|
7828
|
+
if (isMatrix(n)) {
|
|
7829
|
+
for (let i of n) {
|
|
7830
|
+
for (let j of i) {
|
|
7831
|
+
if (typeof j.value === "number") {
|
|
7832
|
+
count += 1;
|
|
7833
|
+
}
|
|
7834
|
+
}
|
|
7835
|
+
}
|
|
7836
|
+
}
|
|
7837
|
+
else {
|
|
7838
|
+
const value = n?.value;
|
|
7839
|
+
if (!isEvaluationError(value) &&
|
|
7840
|
+
(typeof value !== "string" || isNumber(value, locale) || parseDateTime(value, locale))) {
|
|
7841
|
+
count += 1;
|
|
7842
|
+
}
|
|
7843
|
+
}
|
|
7844
|
+
}
|
|
7845
|
+
return count;
|
|
7846
|
+
}
|
|
7847
|
+
function countAny(values) {
|
|
7848
|
+
return reduceAny(values, (acc, a) => (a !== undefined && a.value !== null ? acc + 1 : acc), 0);
|
|
7849
|
+
}
|
|
7850
|
+
function max(values, locale) {
|
|
7851
|
+
const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, locale);
|
|
7852
|
+
return result === -Infinity ? 0 : result;
|
|
7853
|
+
}
|
|
7854
|
+
function min(values, locale) {
|
|
7855
|
+
const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, locale);
|
|
7856
|
+
return result === Infinity ? 0 : result;
|
|
7857
|
+
}
|
|
7858
|
+
|
|
7859
|
+
const pivotTimeAdapterRegistry = new Registry();
|
|
7860
|
+
function pivotTimeAdapter(granularity) {
|
|
7861
|
+
return pivotTimeAdapterRegistry.get(granularity);
|
|
7862
|
+
}
|
|
7863
|
+
/**
|
|
7864
|
+
* The Time Adapter: Managing Time Periods for Pivot Functions
|
|
7865
|
+
*
|
|
7866
|
+
* Overview:
|
|
7867
|
+
* A time adapter is responsible for managing time periods associated with pivot functions.
|
|
7868
|
+
* Each type of period (day, week, month, quarter, etc.) has its own dedicated adapter.
|
|
7869
|
+
* The adapter's primary role is to normalize period values between spreadsheet functions,
|
|
7870
|
+
* and the pivot.
|
|
7871
|
+
* By normalizing the period value, it can be stored consistently in the pivot.
|
|
7872
|
+
*
|
|
7873
|
+
* Normalization Process:
|
|
7874
|
+
* When working with functions in the spreadsheet, the time adapter normalizes
|
|
7875
|
+
* the provided period to facilitate accurate lookup of values in the pivot.
|
|
7876
|
+
* For instance, if the spreadsheet function represents a day period as a number generated
|
|
7877
|
+
* by the DATE function (DATE(2023, 12, 25)), the time adapter will normalize it accordingly.
|
|
7878
|
+
*
|
|
7879
|
+
*/
|
|
7880
|
+
/**
|
|
7881
|
+
* Normalized value: "12/25/2023"
|
|
7882
|
+
*
|
|
7883
|
+
* Note: Those two format are equivalent:
|
|
7884
|
+
* - "MM/dd/yyyy" (luxon format)
|
|
7885
|
+
* - "mm/dd/yyyy" (spreadsheet format)
|
|
7886
|
+
**/
|
|
7887
|
+
const dayAdapter = {
|
|
7888
|
+
normalizeFunctionValue(value) {
|
|
7889
|
+
const date = toNumber(value, DEFAULT_LOCALE);
|
|
7890
|
+
return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/dd/yyyy" });
|
|
7891
|
+
},
|
|
7892
|
+
getFormat(locale) {
|
|
7893
|
+
return (locale ?? DEFAULT_LOCALE).dateFormat;
|
|
7894
|
+
},
|
|
7895
|
+
formatValue(normalizedValue, locale) {
|
|
7896
|
+
locale = locale ?? DEFAULT_LOCALE;
|
|
7897
|
+
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
7898
|
+
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
7899
|
+
},
|
|
7900
|
+
toCellValue(normalizedValue) {
|
|
7901
|
+
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
7902
|
+
},
|
|
7903
|
+
};
|
|
7904
|
+
/**
|
|
7905
|
+
* normalizes day of month number
|
|
7906
|
+
*/
|
|
7907
|
+
const dayOfMonthAdapter = {
|
|
7908
|
+
normalizeFunctionValue(value) {
|
|
7909
|
+
const day = toNumber(value, DEFAULT_LOCALE);
|
|
7910
|
+
if (day < 1 || day > 31) {
|
|
7911
|
+
throw new EvaluationError(_t("%s is not a valid day of month (it should be a number between 1 and 31)", day));
|
|
7912
|
+
}
|
|
7913
|
+
return day;
|
|
7914
|
+
},
|
|
7915
|
+
getFormat() {
|
|
7916
|
+
return "0";
|
|
7917
|
+
},
|
|
7918
|
+
formatValue(normalizedValue, locale) {
|
|
7919
|
+
locale = locale ?? DEFAULT_LOCALE;
|
|
7920
|
+
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
7921
|
+
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
7922
|
+
},
|
|
7923
|
+
toCellValue(normalizedValue) {
|
|
7924
|
+
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
7925
|
+
},
|
|
7926
|
+
};
|
|
7927
|
+
/**
|
|
7928
|
+
* Normalized value: "2/2023" for week 2 of 2023
|
|
7929
|
+
*/
|
|
7930
|
+
const weekAdapter = {
|
|
7931
|
+
normalizeFunctionValue(value) {
|
|
7932
|
+
const [week, year] = value.split("/");
|
|
7933
|
+
return `${Number(week)}/${Number(year)}`;
|
|
7934
|
+
},
|
|
7935
|
+
getFormat() {
|
|
7936
|
+
return undefined;
|
|
7937
|
+
},
|
|
7938
|
+
formatValue(normalizedValue) {
|
|
7939
|
+
const [week, year] = normalizedValue.split("/");
|
|
7940
|
+
return _t("W%(week)s %(year)s", { week, year });
|
|
7941
|
+
},
|
|
7942
|
+
toCellValue(normalizedValue) {
|
|
7943
|
+
return this.formatValue(normalizedValue);
|
|
7944
|
+
},
|
|
7945
|
+
};
|
|
7946
|
+
/**
|
|
7947
|
+
* normalizes iso week number
|
|
7948
|
+
*/
|
|
7949
|
+
const isoWeekNumberAdapter = {
|
|
7950
|
+
normalizeFunctionValue(value) {
|
|
7951
|
+
const isoWeek = toNumber(value, DEFAULT_LOCALE);
|
|
7952
|
+
if (isoWeek < 0 || isoWeek > 53) {
|
|
7953
|
+
throw new EvaluationError(_t("%s is not a valid week (it should be a number between 0 and 53)", isoWeek));
|
|
7954
|
+
}
|
|
7955
|
+
return isoWeek;
|
|
7956
|
+
},
|
|
7957
|
+
getFormat() {
|
|
7958
|
+
return "0";
|
|
7959
|
+
},
|
|
7960
|
+
formatValue(normalizedValue, locale) {
|
|
7961
|
+
locale = locale ?? DEFAULT_LOCALE;
|
|
7962
|
+
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
7963
|
+
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
7964
|
+
},
|
|
7965
|
+
toCellValue(normalizedValue) {
|
|
7966
|
+
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
7967
|
+
},
|
|
7968
|
+
};
|
|
7969
|
+
/**
|
|
7970
|
+
* normalized month value is a string formatted as "MM/yyyy" (luxon format)
|
|
7971
|
+
* e.g. "01/2020" for January 2020
|
|
7972
|
+
*/
|
|
7973
|
+
const monthAdapter = {
|
|
7974
|
+
normalizeFunctionValue(value) {
|
|
7975
|
+
const date = toNumber(value, DEFAULT_LOCALE);
|
|
7976
|
+
return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
|
|
7977
|
+
},
|
|
7978
|
+
getFormat() {
|
|
7979
|
+
return "mmmm yyyy";
|
|
7980
|
+
},
|
|
7981
|
+
formatValue(normalizedValue, locale) {
|
|
7982
|
+
locale = locale ?? DEFAULT_LOCALE;
|
|
7983
|
+
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
7984
|
+
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
7985
|
+
},
|
|
7986
|
+
toCellValue(normalizedValue) {
|
|
7987
|
+
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
7988
|
+
},
|
|
7989
|
+
};
|
|
7990
|
+
/**
|
|
7991
|
+
* normalizes month number
|
|
7992
|
+
*/
|
|
7993
|
+
const monthNumberAdapter = {
|
|
7994
|
+
normalizeFunctionValue(value) {
|
|
7995
|
+
const month = toNumber(value, DEFAULT_LOCALE);
|
|
7996
|
+
if (month < 1 || month > 12) {
|
|
7997
|
+
throw new EvaluationError(_t("%s is not a valid month (it should be a number between 1 and 12)", month));
|
|
7998
|
+
}
|
|
7999
|
+
return month;
|
|
8000
|
+
},
|
|
8001
|
+
getFormat() {
|
|
8002
|
+
return "0";
|
|
8003
|
+
},
|
|
8004
|
+
formatValue(normalizedValue, locale) {
|
|
8005
|
+
locale = locale ?? DEFAULT_LOCALE;
|
|
8006
|
+
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
8007
|
+
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
8008
|
+
},
|
|
8009
|
+
toCellValue(normalizedValue) {
|
|
8010
|
+
return MONTHS[toNumber(normalizedValue, DEFAULT_LOCALE) - 1].toString();
|
|
8011
|
+
},
|
|
8012
|
+
};
|
|
8013
|
+
/**
|
|
8014
|
+
* normalized quarter value is "quarter/year"
|
|
8015
|
+
* e.g. "1/2020" for Q1 2020
|
|
8016
|
+
*/
|
|
8017
|
+
const quarterAdapter = {
|
|
8018
|
+
normalizeFunctionValue(value) {
|
|
8019
|
+
const [quarter, year] = value.split("/");
|
|
8020
|
+
return `${quarter}/${year}`;
|
|
8021
|
+
},
|
|
8022
|
+
getFormat() {
|
|
8023
|
+
return undefined;
|
|
8024
|
+
},
|
|
8025
|
+
formatValue(normalizedValue) {
|
|
8026
|
+
const [quarter, year] = normalizedValue.split("/");
|
|
8027
|
+
return _t("Q%(quarter)s %(year)s", { quarter, year });
|
|
8028
|
+
},
|
|
8029
|
+
toCellValue(normalizedValue) {
|
|
8030
|
+
return this.formatValue(normalizedValue);
|
|
8031
|
+
},
|
|
8032
|
+
};
|
|
8033
|
+
/**
|
|
8034
|
+
* normalizes quarter number
|
|
8035
|
+
*/
|
|
8036
|
+
const quarterNumberAdapter = {
|
|
8037
|
+
normalizeFunctionValue(value) {
|
|
8038
|
+
const quarter = toNumber(value, DEFAULT_LOCALE);
|
|
8039
|
+
if (quarter < 1 || quarter > 4) {
|
|
8040
|
+
throw new EvaluationError(_t("%s is not a valid quarter (it should be a number between 1 and 4)", quarter));
|
|
8041
|
+
}
|
|
8042
|
+
return quarter;
|
|
8043
|
+
},
|
|
8044
|
+
getFormat() {
|
|
8045
|
+
return "0";
|
|
8046
|
+
},
|
|
8047
|
+
formatValue(normalizedValue, locale) {
|
|
8048
|
+
locale = locale ?? DEFAULT_LOCALE;
|
|
8049
|
+
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
8050
|
+
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
8051
|
+
},
|
|
8052
|
+
toCellValue(normalizedValue) {
|
|
8053
|
+
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
8054
|
+
},
|
|
8055
|
+
};
|
|
8056
|
+
const yearAdapter = {
|
|
8057
|
+
normalizeFunctionValue(value) {
|
|
8058
|
+
return toNumber(value, DEFAULT_LOCALE);
|
|
8059
|
+
},
|
|
8060
|
+
getFormat() {
|
|
8061
|
+
return "0";
|
|
8062
|
+
},
|
|
8063
|
+
formatValue(normalizedValue, locale) {
|
|
8064
|
+
locale = locale ?? DEFAULT_LOCALE;
|
|
8065
|
+
return formatValue(normalizedValue, { locale, format: "0" });
|
|
8066
|
+
},
|
|
8067
|
+
toCellValue(normalizedValue) {
|
|
8068
|
+
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
8069
|
+
},
|
|
8070
|
+
};
|
|
8071
|
+
pivotTimeAdapterRegistry
|
|
8072
|
+
.add("day", dayAdapter)
|
|
8073
|
+
.add("week", weekAdapter)
|
|
8074
|
+
.add("month", monthAdapter)
|
|
8075
|
+
.add("quarter", quarterAdapter)
|
|
8076
|
+
.add("year", yearAdapter)
|
|
8077
|
+
.add("day_of_month", dayOfMonthAdapter)
|
|
8078
|
+
.add("iso_week_number", isoWeekNumberAdapter)
|
|
8079
|
+
.add("month_number", monthNumberAdapter)
|
|
8080
|
+
.add("quarter_number", quarterNumberAdapter)
|
|
8081
|
+
.add("year_number", yearAdapter);
|
|
8082
|
+
|
|
8083
|
+
const AGGREGATOR_NAMES = {
|
|
8084
|
+
count: _t("Count"),
|
|
8085
|
+
count_distinct: _t("Count Distinct"),
|
|
8086
|
+
bool_and: _t("Boolean And"),
|
|
8087
|
+
bool_or: _t("Boolean Or"),
|
|
8088
|
+
max: _t("Maximum"),
|
|
8089
|
+
min: _t("Minimum"),
|
|
8090
|
+
avg: _t("Average"),
|
|
8091
|
+
sum: _t("Sum"),
|
|
8092
|
+
};
|
|
8093
|
+
const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
|
|
8094
|
+
const AGGREGATORS_BY_FIELD_TYPE = {
|
|
8095
|
+
integer: NUMBER_CHAR_AGGREGATORS,
|
|
8096
|
+
char: NUMBER_CHAR_AGGREGATORS,
|
|
8097
|
+
boolean: ["count_distinct", "count", "bool_and", "bool_or"],
|
|
8098
|
+
};
|
|
8099
|
+
const AGGREGATORS = {};
|
|
8100
|
+
for (const type in AGGREGATORS_BY_FIELD_TYPE) {
|
|
8101
|
+
AGGREGATORS[type] = {};
|
|
8102
|
+
for (const aggregator of AGGREGATORS_BY_FIELD_TYPE[type]) {
|
|
8103
|
+
AGGREGATORS[type][aggregator] = AGGREGATOR_NAMES[aggregator];
|
|
8104
|
+
}
|
|
8105
|
+
}
|
|
8106
|
+
const AGGREGATORS_FN = {
|
|
8107
|
+
count: {
|
|
8108
|
+
fn: (args) => countAny([args]),
|
|
8109
|
+
format: () => "0",
|
|
8110
|
+
},
|
|
8111
|
+
count_distinct: {
|
|
8112
|
+
fn: (args) => countUnique([args]),
|
|
8113
|
+
format: () => "0",
|
|
8114
|
+
},
|
|
8115
|
+
bool_and: {
|
|
8116
|
+
fn: (args) => boolAnd([args]).result,
|
|
8117
|
+
format: () => undefined,
|
|
8118
|
+
},
|
|
8119
|
+
bool_or: {
|
|
8120
|
+
fn: (args) => boolOr([args]).result,
|
|
8121
|
+
format: () => undefined,
|
|
8122
|
+
},
|
|
8123
|
+
max: {
|
|
8124
|
+
fn: (args, locale) => max([args], locale),
|
|
8125
|
+
format: inferFormat,
|
|
8126
|
+
},
|
|
8127
|
+
min: {
|
|
8128
|
+
fn: (args, locale) => min([args], locale),
|
|
8129
|
+
format: inferFormat,
|
|
8130
|
+
},
|
|
8131
|
+
avg: {
|
|
8132
|
+
fn: (args, locale) => average([args], locale),
|
|
8133
|
+
format: inferFormat,
|
|
8134
|
+
},
|
|
8135
|
+
sum: {
|
|
8136
|
+
fn: (args, locale) => sum([args], locale),
|
|
8137
|
+
format: inferFormat,
|
|
8138
|
+
},
|
|
8139
|
+
};
|
|
8140
|
+
function makePivotFormulaFromPivotCell(pivotFormulaId, pivotCell) {
|
|
8141
|
+
switch (pivotCell.type) {
|
|
8142
|
+
case "HEADER":
|
|
8143
|
+
return makePivotFormula("PIVOT.HEADER", [pivotFormulaId, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
|
|
8144
|
+
case "MEASURE_HEADER":
|
|
8145
|
+
return makePivotFormula("PIVOT.HEADER", [pivotFormulaId, ...flatPivotDomain(pivotCell.domain), "measure", pivotCell.measure].filter(isDefined));
|
|
8146
|
+
case "VALUE":
|
|
8147
|
+
return makePivotFormula("PIVOT.VALUE", [pivotFormulaId, pivotCell.measure, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
|
|
8148
|
+
case "EMPTY":
|
|
8149
|
+
return "";
|
|
8150
|
+
}
|
|
8151
|
+
}
|
|
8152
|
+
/**
|
|
8153
|
+
* Build a pivot formula expression
|
|
8154
|
+
*/
|
|
8155
|
+
function makePivotFormula(formula, args) {
|
|
8156
|
+
return `=${formula}(${args
|
|
8157
|
+
.map((arg) => {
|
|
8158
|
+
const stringIsNumber = typeof arg == "string" && !isNaN(Number(arg)) && Number(arg).toString() === arg;
|
|
8159
|
+
const convertToNumber = typeof arg == "number" || stringIsNumber;
|
|
8160
|
+
return convertToNumber ? `${arg}` : `"${arg.toString().replace(/"/g, '\\"')}"`;
|
|
8161
|
+
})
|
|
8162
|
+
.join(",")})`;
|
|
8163
|
+
}
|
|
8164
|
+
/**
|
|
8165
|
+
* Given an object of form {"1": {...}, "2": {...}, ...} get the maximum ID used
|
|
8166
|
+
* in this object
|
|
8167
|
+
* If the object has no keys, return 0
|
|
8168
|
+
*
|
|
8169
|
+
*/
|
|
8170
|
+
function getMaxObjectId(o) {
|
|
8171
|
+
const keys = Object.keys(o);
|
|
8172
|
+
if (!keys.length) {
|
|
8173
|
+
return 0;
|
|
8174
|
+
}
|
|
8175
|
+
const nums = keys.map((id) => parseInt(id, 10));
|
|
8176
|
+
const max = Math.max(...nums);
|
|
8177
|
+
return max;
|
|
8178
|
+
}
|
|
8179
|
+
const ALL_PERIODS = {
|
|
8180
|
+
year: _t("Year"),
|
|
8181
|
+
quarter: _t("Quarter"),
|
|
8182
|
+
month: _t("Month"),
|
|
8183
|
+
week: _t("Week"),
|
|
8184
|
+
day: _t("Day"),
|
|
8185
|
+
year_number: _t("Year"),
|
|
8186
|
+
quarter_number: _t("Quarter"),
|
|
8187
|
+
month_number: _t("Month"),
|
|
8188
|
+
iso_week_number: _t("Week"),
|
|
8189
|
+
day_of_month: _t("Day of Month"),
|
|
8190
|
+
};
|
|
8191
|
+
const DATE_FIELDS = ["date", "datetime"];
|
|
8192
|
+
/**
|
|
8193
|
+
* Parse a dimension string into a pivot dimension definition.
|
|
8194
|
+
* e.g "create_date:month" => { name: "create_date", granularity: "month" }
|
|
8195
|
+
*/
|
|
8196
|
+
function parseDimension(dimension) {
|
|
8197
|
+
const [name, granularity] = dimension.split(":");
|
|
8198
|
+
if (granularity) {
|
|
8199
|
+
return { name, granularity };
|
|
8200
|
+
}
|
|
8201
|
+
return { name };
|
|
8202
|
+
}
|
|
8203
|
+
function isDateField(field) {
|
|
8204
|
+
return DATE_FIELDS.includes(field.type);
|
|
8205
|
+
}
|
|
8206
|
+
function toPivotDomain(domainStr) {
|
|
8207
|
+
if (domainStr.length % 2 !== 0) {
|
|
8208
|
+
throw new Error("Invalid domain: odd number of elements");
|
|
8209
|
+
}
|
|
8210
|
+
const domain = [];
|
|
8211
|
+
for (let i = 0; i < domainStr.length - 1; i += 2) {
|
|
8212
|
+
domain.push({ field: domainStr[i], value: domainStr[i + 1] });
|
|
8213
|
+
}
|
|
8214
|
+
return domain;
|
|
8215
|
+
}
|
|
8216
|
+
function flatPivotDomain(domain) {
|
|
8217
|
+
return domain.flatMap((arg) => [arg.field, arg.value]);
|
|
8218
|
+
}
|
|
8219
|
+
/**
|
|
8220
|
+
* Parses the value defining a pivot group in a PIVOT formula
|
|
8221
|
+
* e.g. given the following formula PIVOT.VALUE("1", "stage_id", "42", "status", "won"),
|
|
8222
|
+
* the two group values are "42" and "won".
|
|
8223
|
+
*/
|
|
8224
|
+
function toNormalizedPivotValue(dimension, groupValue) {
|
|
8225
|
+
if (groupValue === null || groupValue === "null") {
|
|
8226
|
+
return null;
|
|
8227
|
+
}
|
|
8228
|
+
const groupValueString = typeof groupValue === "boolean"
|
|
8229
|
+
? toString(groupValue).toLocaleLowerCase()
|
|
8230
|
+
: toString(groupValue);
|
|
8231
|
+
if (!pivotNormalizationValueRegistry.contains(dimension.type)) {
|
|
8232
|
+
throw new EvaluationError(_t("Field %(field)s is not supported because of its type (%(type)s)", {
|
|
8233
|
+
field: dimension.displayName,
|
|
8234
|
+
type: dimension.type,
|
|
8235
|
+
}));
|
|
8236
|
+
}
|
|
8237
|
+
// represents a field which is not set (=False server side)
|
|
8238
|
+
if (groupValueString === "false") {
|
|
8239
|
+
return false;
|
|
8240
|
+
}
|
|
8241
|
+
const normalizer = pivotNormalizationValueRegistry.get(dimension.type);
|
|
8242
|
+
return normalizer(groupValueString, dimension.granularity);
|
|
8243
|
+
}
|
|
8244
|
+
function normalizeDateTime(value, granularity) {
|
|
8245
|
+
if (!granularity) {
|
|
8246
|
+
throw "";
|
|
8247
|
+
}
|
|
8248
|
+
return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
|
|
8249
|
+
}
|
|
8250
|
+
const pivotNormalizationValueRegistry = new Registry();
|
|
8251
|
+
pivotNormalizationValueRegistry
|
|
8252
|
+
.add("date", normalizeDateTime)
|
|
8253
|
+
.add("datetime", normalizeDateTime)
|
|
8254
|
+
.add("integer", (value) => toNumber(value, DEFAULT_LOCALE))
|
|
8255
|
+
.add("boolean", (value) => toBoolean(value))
|
|
8256
|
+
.add("char", (value) => toString(value));
|
|
8257
|
+
|
|
7758
8258
|
/**
|
|
7759
8259
|
* Change the reference types inside the given token, if the token represent a range or a cell
|
|
7760
8260
|
*
|
|
@@ -7953,6 +8453,11 @@ const CellIsOperators = {
|
|
|
7953
8453
|
const ChartTerms = {
|
|
7954
8454
|
Series: _t("Series"),
|
|
7955
8455
|
BackgroundColor: _t("Background color"),
|
|
8456
|
+
StackedBarChart: _t("Stacked bar chart"),
|
|
8457
|
+
StackedLineChart: _t("Stacked line chart"),
|
|
8458
|
+
CumulativeData: _t("Cumulative data"),
|
|
8459
|
+
TreatLabelsAsText: _t("Treat labels as text"),
|
|
8460
|
+
AggregatedChart: _t("Aggregate"),
|
|
7956
8461
|
Errors: {
|
|
7957
8462
|
Unexpected: _t("The chart definition is invalid for an unknown reason"),
|
|
7958
8463
|
// BASIC CHART ERRORS (LINE | BAR | PIE)
|
|
@@ -8885,6 +9390,7 @@ function drawHighlight(renderingContext, highlight, rect) {
|
|
|
8885
9390
|
}
|
|
8886
9391
|
const color = highlight.color || HIGHLIGHT_COLOR;
|
|
8887
9392
|
const { ctx } = renderingContext;
|
|
9393
|
+
ctx.save();
|
|
8888
9394
|
if (!highlight.noBorder) {
|
|
8889
9395
|
if (highlight.dashed) {
|
|
8890
9396
|
ctx.setLineDash([5, 3]);
|
|
@@ -8904,6 +9410,7 @@ function drawHighlight(renderingContext, highlight, rect) {
|
|
|
8904
9410
|
ctx.fillStyle = setColorAlpha(toHex(color), highlight.fillAlpha ?? 0.12);
|
|
8905
9411
|
ctx.fillRect(x, y, width, height);
|
|
8906
9412
|
}
|
|
9413
|
+
ctx.restore();
|
|
8907
9414
|
}
|
|
8908
9415
|
|
|
8909
9416
|
class HighlightStore extends SpreadsheetStore {
|
|
@@ -9434,8 +9941,20 @@ class ComposerStore extends SpreadsheetStore {
|
|
|
9434
9941
|
replaceSelectedRange(zone) {
|
|
9435
9942
|
const ref = this.getZoneReference(zone);
|
|
9436
9943
|
const currentToken = this.tokenAtCursor;
|
|
9437
|
-
|
|
9438
|
-
|
|
9944
|
+
let replaceStart = this.selectionStart;
|
|
9945
|
+
if (currentToken?.type === "REFERENCE") {
|
|
9946
|
+
replaceStart = currentToken.start;
|
|
9947
|
+
}
|
|
9948
|
+
else if (currentToken?.type === "RIGHT_PAREN") {
|
|
9949
|
+
// match left parenthesis
|
|
9950
|
+
const leftParenthesisIndex = this.currentTokens.findIndex((token) => token.type === "LEFT_PAREN" && token.parenIndex === currentToken.parenIndex);
|
|
9951
|
+
const functionToken = this.currentTokens[leftParenthesisIndex - 1];
|
|
9952
|
+
if (functionToken === undefined) {
|
|
9953
|
+
return;
|
|
9954
|
+
}
|
|
9955
|
+
replaceStart = functionToken.start;
|
|
9956
|
+
}
|
|
9957
|
+
this.replaceText(ref, replaceStart, this.selectionEnd);
|
|
9439
9958
|
}
|
|
9440
9959
|
/**
|
|
9441
9960
|
* Replace the reference of the old zone by the new one.
|
|
@@ -9455,10 +9974,8 @@ class ComposerStore extends SpreadsheetStore {
|
|
|
9455
9974
|
const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);
|
|
9456
9975
|
return isEqual(this.getters.expandZone(activeSheetId, refRange.zone), oldZone);
|
|
9457
9976
|
});
|
|
9458
|
-
// this function assumes that the previous range is always found because
|
|
9459
|
-
// it's called when changing a highlight, which exists by definition
|
|
9460
9977
|
if (!previousRefToken) {
|
|
9461
|
-
|
|
9978
|
+
return;
|
|
9462
9979
|
}
|
|
9463
9980
|
const previousRange = this.getters.getRangeFromSheetXC(activeSheetId, previousRefToken.value);
|
|
9464
9981
|
this.selectionStart = previousRefToken.start;
|
|
@@ -9470,6 +9987,17 @@ class ComposerStore extends SpreadsheetStore {
|
|
|
9470
9987
|
getZoneReference(zone) {
|
|
9471
9988
|
const inputSheetId = this.currentEditedCell.sheetId;
|
|
9472
9989
|
const sheetId = this.getters.getActiveSheetId();
|
|
9990
|
+
if (zone.top === zone.bottom && zone.left === zone.right) {
|
|
9991
|
+
const position = { sheetId, col: zone.left, row: zone.top };
|
|
9992
|
+
const pivotId = this.getters.getPivotIdFromPosition(position);
|
|
9993
|
+
const pivotCell = this.getters.getPivotCellFromPosition(position);
|
|
9994
|
+
const cell = this.getters.getCell(position);
|
|
9995
|
+
if (pivotId && pivotCell.type !== "EMPTY" && !cell?.isFormula) {
|
|
9996
|
+
const formulaPivotId = this.getters.getPivotFormulaId(pivotId);
|
|
9997
|
+
const formula = makePivotFormulaFromPivotCell(formulaPivotId, pivotCell);
|
|
9998
|
+
return formula.slice(1); // strip leading =
|
|
9999
|
+
}
|
|
10000
|
+
}
|
|
9473
10001
|
const range = this.getters.getRangeFromZone(sheetId, zone);
|
|
9474
10002
|
return this.getters.getSelectionRangeString(range, inputSheetId);
|
|
9475
10003
|
}
|
|
@@ -9572,19 +10100,35 @@ class ComposerStore extends SpreadsheetStore {
|
|
|
9572
10100
|
const colorIndex = this.colorIndexByRange[rangeString];
|
|
9573
10101
|
return colors$1[colorIndex % colors$1.length];
|
|
9574
10102
|
};
|
|
9575
|
-
|
|
10103
|
+
const highlights = [];
|
|
10104
|
+
for (const range of this.getReferencedRanges()) {
|
|
9576
10105
|
const rangeString = this.getters.getRangeString(range, editionSheetId);
|
|
9577
10106
|
const { numberOfRows, numberOfCols } = zoneToDimension(range.zone);
|
|
9578
10107
|
const zone = numberOfRows * numberOfCols === 1
|
|
9579
10108
|
? this.getters.expandZone(range.sheetId, range.zone)
|
|
9580
10109
|
: range.zone;
|
|
9581
|
-
|
|
10110
|
+
highlights.push({
|
|
9582
10111
|
zone,
|
|
9583
10112
|
color: rangeColor(rangeString),
|
|
9584
10113
|
sheetId: range.sheetId,
|
|
9585
10114
|
interactive: true,
|
|
9586
|
-
};
|
|
9587
|
-
}
|
|
10115
|
+
});
|
|
10116
|
+
}
|
|
10117
|
+
const activeSheetId = this.getters.getActiveSheetId();
|
|
10118
|
+
const selectionZone = this.model.selection.getAnchor().zone;
|
|
10119
|
+
const isSelectionHightlighted = highlights.find((highlight) => highlight.sheetId === activeSheetId && isEqual(highlight.zone, selectionZone));
|
|
10120
|
+
if (this.editionMode === "selecting" && !isSelectionHightlighted) {
|
|
10121
|
+
highlights.push({
|
|
10122
|
+
zone: selectionZone,
|
|
10123
|
+
color: "#445566",
|
|
10124
|
+
sheetId: activeSheetId,
|
|
10125
|
+
dashed: true,
|
|
10126
|
+
interactive: false,
|
|
10127
|
+
noFill: true,
|
|
10128
|
+
thinLine: true,
|
|
10129
|
+
});
|
|
10130
|
+
}
|
|
10131
|
+
return highlights;
|
|
9588
10132
|
}
|
|
9589
10133
|
/**
|
|
9590
10134
|
* Return ranges currently referenced in the composer
|
|
@@ -9843,8 +10387,14 @@ class ChartJsComponent extends Component {
|
|
|
9843
10387
|
useEffect(() => {
|
|
9844
10388
|
const runtime = this.chartRuntime;
|
|
9845
10389
|
if (!deepEquals(runtime, this.currentRuntime, "ignoreFunctions")) {
|
|
10390
|
+
if (runtime.chartJsConfig.type !== this.currentRuntime.chartJsConfig.type) {
|
|
10391
|
+
this.chart?.destroy();
|
|
10392
|
+
this.createChart(deepCopy(runtime.chartJsConfig));
|
|
10393
|
+
}
|
|
10394
|
+
else {
|
|
10395
|
+
this.updateChartJs(deepCopy(runtime));
|
|
10396
|
+
}
|
|
9846
10397
|
this.currentRuntime = runtime;
|
|
9847
|
-
this.updateChartJs(deepCopy(runtime));
|
|
9848
10398
|
}
|
|
9849
10399
|
});
|
|
9850
10400
|
}
|
|
@@ -10212,6 +10762,9 @@ function getChartAxisTitleRuntime(design) {
|
|
|
10212
10762
|
}
|
|
10213
10763
|
function getDefinedAxis(definition) {
|
|
10214
10764
|
let useLeftAxis = false, useRightAxis = false;
|
|
10765
|
+
if ("horizontal" in definition && definition.horizontal) {
|
|
10766
|
+
return { useLeftAxis: true, useRightAxis: false };
|
|
10767
|
+
}
|
|
10215
10768
|
for (const design of definition.dataSets || []) {
|
|
10216
10769
|
if (design.yAxisId === "y1") {
|
|
10217
10770
|
useRightAxis = true;
|
|
@@ -11779,13 +12332,6 @@ var misc = /*#__PURE__*/Object.freeze({
|
|
|
11779
12332
|
FORMAT_LARGE_NUMBER: FORMAT_LARGE_NUMBER
|
|
11780
12333
|
});
|
|
11781
12334
|
|
|
11782
|
-
function sum(values, locale) {
|
|
11783
|
-
return reduceNumbers(values, (acc, a) => acc + a, 0, locale);
|
|
11784
|
-
}
|
|
11785
|
-
function countUnique(args) {
|
|
11786
|
-
return reduceAny(args, (acc, a) => (isDataNonEmpty(a) ? acc.add(a?.value) : acc), new Set()).size;
|
|
11787
|
-
}
|
|
11788
|
-
|
|
11789
12335
|
const DEFAULT_FACTOR = 1;
|
|
11790
12336
|
const DEFAULT_MODE = 0;
|
|
11791
12337
|
const DEFAULT_PLACES = 0;
|
|
@@ -12899,53 +13445,6 @@ var math = /*#__PURE__*/Object.freeze({
|
|
|
12899
13445
|
TRUNC: TRUNC
|
|
12900
13446
|
});
|
|
12901
13447
|
|
|
12902
|
-
function assertSameNumberOfElements(...args) {
|
|
12903
|
-
const dims = args[0].length;
|
|
12904
|
-
args.forEach((arg, i) => assert(() => arg.length === dims, _t("[[FUNCTION_NAME]] has mismatched dimensions for argument %s (%s vs %s).", i.toString(), dims.toString(), arg.length.toString())));
|
|
12905
|
-
}
|
|
12906
|
-
function average(values, locale) {
|
|
12907
|
-
let count = 0;
|
|
12908
|
-
const sum = reduceNumbers(values, (acc, a) => {
|
|
12909
|
-
count += 1;
|
|
12910
|
-
return acc + a;
|
|
12911
|
-
}, 0, locale);
|
|
12912
|
-
assertNotZero(count);
|
|
12913
|
-
return sum / count;
|
|
12914
|
-
}
|
|
12915
|
-
function countNumbers(values, locale) {
|
|
12916
|
-
let count = 0;
|
|
12917
|
-
for (let n of values) {
|
|
12918
|
-
if (isMatrix(n)) {
|
|
12919
|
-
for (let i of n) {
|
|
12920
|
-
for (let j of i) {
|
|
12921
|
-
if (typeof j.value === "number") {
|
|
12922
|
-
count += 1;
|
|
12923
|
-
}
|
|
12924
|
-
}
|
|
12925
|
-
}
|
|
12926
|
-
}
|
|
12927
|
-
else {
|
|
12928
|
-
const value = n?.value;
|
|
12929
|
-
if (!isEvaluationError(value) &&
|
|
12930
|
-
(typeof value !== "string" || isNumber(value, locale) || parseDateTime(value, locale))) {
|
|
12931
|
-
count += 1;
|
|
12932
|
-
}
|
|
12933
|
-
}
|
|
12934
|
-
}
|
|
12935
|
-
return count;
|
|
12936
|
-
}
|
|
12937
|
-
function countAny(values) {
|
|
12938
|
-
return reduceAny(values, (acc, a) => (a !== undefined && a.value !== null ? acc + 1 : acc), 0);
|
|
12939
|
-
}
|
|
12940
|
-
function max(values, locale) {
|
|
12941
|
-
const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, locale);
|
|
12942
|
-
return result === -Infinity ? 0 : result;
|
|
12943
|
-
}
|
|
12944
|
-
function min(values, locale) {
|
|
12945
|
-
const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, locale);
|
|
12946
|
-
return result === Infinity ? 0 : result;
|
|
12947
|
-
}
|
|
12948
|
-
|
|
12949
13448
|
function filterAndFlatData(dataY, dataX) {
|
|
12950
13449
|
const _flatDataY = [];
|
|
12951
13450
|
const _flatDataX = [];
|
|
@@ -13501,7 +14000,7 @@ const LARGE = {
|
|
|
13501
14000
|
// LINEST
|
|
13502
14001
|
// -----------------------------------------------------------------------------
|
|
13503
14002
|
const LINEST = {
|
|
13504
|
-
description: _t("
|
|
14003
|
+
description: _t("Given partial data about a linear trend, calculates various parameters about the ideal linear trend using the least-squares method."),
|
|
13505
14004
|
args: [
|
|
13506
14005
|
arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
|
|
13507
14006
|
arg("data_x (range<number>, default={1;2;3;...})", _t("The range representing the array or matrix of independent data.")),
|
|
@@ -13517,7 +14016,7 @@ const LINEST = {
|
|
|
13517
14016
|
// LOGEST
|
|
13518
14017
|
// -----------------------------------------------------------------------------
|
|
13519
14018
|
const LOGEST = {
|
|
13520
|
-
description: _t("
|
|
14019
|
+
description: _t("Given partial data about an exponential growth curve, calculates various parameters about the best fit ideal exponential growth curve."),
|
|
13521
14020
|
args: [
|
|
13522
14021
|
arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
|
|
13523
14022
|
arg("data_x (range<number>, optional, default={1;2;3;...})", _t("The range representing the array or matrix of independent data.")),
|
|
@@ -17920,33 +18419,6 @@ var info = /*#__PURE__*/Object.freeze({
|
|
|
17920
18419
|
NA: NA
|
|
17921
18420
|
});
|
|
17922
18421
|
|
|
17923
|
-
function boolAnd(args) {
|
|
17924
|
-
let foundBoolean = false;
|
|
17925
|
-
let acc = true;
|
|
17926
|
-
conditionalVisitBoolean(args, (arg) => {
|
|
17927
|
-
foundBoolean = true;
|
|
17928
|
-
acc = acc && arg;
|
|
17929
|
-
return acc;
|
|
17930
|
-
});
|
|
17931
|
-
return {
|
|
17932
|
-
foundBoolean,
|
|
17933
|
-
result: acc,
|
|
17934
|
-
};
|
|
17935
|
-
}
|
|
17936
|
-
function boolOr(args) {
|
|
17937
|
-
let foundBoolean = false;
|
|
17938
|
-
let acc = false;
|
|
17939
|
-
conditionalVisitBoolean(args, (arg) => {
|
|
17940
|
-
foundBoolean = true;
|
|
17941
|
-
acc = acc || arg;
|
|
17942
|
-
return !acc;
|
|
17943
|
-
});
|
|
17944
|
-
return {
|
|
17945
|
-
foundBoolean,
|
|
17946
|
-
result: acc,
|
|
17947
|
-
};
|
|
17948
|
-
}
|
|
17949
|
-
|
|
17950
18422
|
// -----------------------------------------------------------------------------
|
|
17951
18423
|
// AND
|
|
17952
18424
|
// -----------------------------------------------------------------------------
|
|
@@ -18144,393 +18616,6 @@ var logical = /*#__PURE__*/Object.freeze({
|
|
|
18144
18616
|
XOR: XOR
|
|
18145
18617
|
});
|
|
18146
18618
|
|
|
18147
|
-
const pivotTimeAdapterRegistry = new Registry();
|
|
18148
|
-
function pivotTimeAdapter(granularity) {
|
|
18149
|
-
return pivotTimeAdapterRegistry.get(granularity);
|
|
18150
|
-
}
|
|
18151
|
-
/**
|
|
18152
|
-
* The Time Adapter: Managing Time Periods for Pivot Functions
|
|
18153
|
-
*
|
|
18154
|
-
* Overview:
|
|
18155
|
-
* A time adapter is responsible for managing time periods associated with pivot functions.
|
|
18156
|
-
* Each type of period (day, week, month, quarter, etc.) has its own dedicated adapter.
|
|
18157
|
-
* The adapter's primary role is to normalize period values between spreadsheet functions,
|
|
18158
|
-
* and the pivot.
|
|
18159
|
-
* By normalizing the period value, it can be stored consistently in the pivot.
|
|
18160
|
-
*
|
|
18161
|
-
* Normalization Process:
|
|
18162
|
-
* When working with functions in the spreadsheet, the time adapter normalizes
|
|
18163
|
-
* the provided period to facilitate accurate lookup of values in the pivot.
|
|
18164
|
-
* For instance, if the spreadsheet function represents a day period as a number generated
|
|
18165
|
-
* by the DATE function (DATE(2023, 12, 25)), the time adapter will normalize it accordingly.
|
|
18166
|
-
*
|
|
18167
|
-
*/
|
|
18168
|
-
/**
|
|
18169
|
-
* Normalized value: "12/25/2023"
|
|
18170
|
-
*
|
|
18171
|
-
* Note: Those two format are equivalent:
|
|
18172
|
-
* - "MM/dd/yyyy" (luxon format)
|
|
18173
|
-
* - "mm/dd/yyyy" (spreadsheet format)
|
|
18174
|
-
**/
|
|
18175
|
-
const dayAdapter = {
|
|
18176
|
-
normalizeFunctionValue(value) {
|
|
18177
|
-
const date = toNumber(value, DEFAULT_LOCALE);
|
|
18178
|
-
return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/dd/yyyy" });
|
|
18179
|
-
},
|
|
18180
|
-
getFormat(locale) {
|
|
18181
|
-
return (locale ?? DEFAULT_LOCALE).dateFormat;
|
|
18182
|
-
},
|
|
18183
|
-
formatValue(normalizedValue, locale) {
|
|
18184
|
-
locale = locale ?? DEFAULT_LOCALE;
|
|
18185
|
-
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18186
|
-
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
18187
|
-
},
|
|
18188
|
-
toCellValue(normalizedValue) {
|
|
18189
|
-
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18190
|
-
},
|
|
18191
|
-
};
|
|
18192
|
-
/**
|
|
18193
|
-
* normalizes day of month number
|
|
18194
|
-
*/
|
|
18195
|
-
const dayOfMonthAdapter = {
|
|
18196
|
-
normalizeFunctionValue(value) {
|
|
18197
|
-
const day = toNumber(value, DEFAULT_LOCALE);
|
|
18198
|
-
if (day < 1 || day > 31) {
|
|
18199
|
-
throw new EvaluationError(_t("%s is not a valid day of month (it should be a number between 1 and 31)", day));
|
|
18200
|
-
}
|
|
18201
|
-
return day;
|
|
18202
|
-
},
|
|
18203
|
-
getFormat() {
|
|
18204
|
-
return "0";
|
|
18205
|
-
},
|
|
18206
|
-
formatValue(normalizedValue, locale) {
|
|
18207
|
-
locale = locale ?? DEFAULT_LOCALE;
|
|
18208
|
-
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18209
|
-
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
18210
|
-
},
|
|
18211
|
-
toCellValue(normalizedValue) {
|
|
18212
|
-
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18213
|
-
},
|
|
18214
|
-
};
|
|
18215
|
-
/**
|
|
18216
|
-
* Normalized value: "2/2023" for week 2 of 2023
|
|
18217
|
-
*/
|
|
18218
|
-
const weekAdapter = {
|
|
18219
|
-
normalizeFunctionValue(value) {
|
|
18220
|
-
const [week, year] = value.split("/");
|
|
18221
|
-
return `${Number(week)}/${Number(year)}`;
|
|
18222
|
-
},
|
|
18223
|
-
getFormat() {
|
|
18224
|
-
return undefined;
|
|
18225
|
-
},
|
|
18226
|
-
formatValue(normalizedValue) {
|
|
18227
|
-
const [week, year] = normalizedValue.split("/");
|
|
18228
|
-
return _t("W%(week)s %(year)s", { week, year });
|
|
18229
|
-
},
|
|
18230
|
-
toCellValue(normalizedValue) {
|
|
18231
|
-
return this.formatValue(normalizedValue);
|
|
18232
|
-
},
|
|
18233
|
-
};
|
|
18234
|
-
/**
|
|
18235
|
-
* normalizes iso week number
|
|
18236
|
-
*/
|
|
18237
|
-
const isoWeekNumberAdapter = {
|
|
18238
|
-
normalizeFunctionValue(value) {
|
|
18239
|
-
const isoWeek = toNumber(value, DEFAULT_LOCALE);
|
|
18240
|
-
if (isoWeek < 0 || isoWeek > 53) {
|
|
18241
|
-
throw new EvaluationError(_t("%s is not a valid week (it should be a number between 0 and 53)", isoWeek));
|
|
18242
|
-
}
|
|
18243
|
-
return isoWeek;
|
|
18244
|
-
},
|
|
18245
|
-
getFormat() {
|
|
18246
|
-
return "0";
|
|
18247
|
-
},
|
|
18248
|
-
formatValue(normalizedValue, locale) {
|
|
18249
|
-
locale = locale ?? DEFAULT_LOCALE;
|
|
18250
|
-
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18251
|
-
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
18252
|
-
},
|
|
18253
|
-
toCellValue(normalizedValue) {
|
|
18254
|
-
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18255
|
-
},
|
|
18256
|
-
};
|
|
18257
|
-
/**
|
|
18258
|
-
* normalized month value is a string formatted as "MM/yyyy" (luxon format)
|
|
18259
|
-
* e.g. "01/2020" for January 2020
|
|
18260
|
-
*/
|
|
18261
|
-
const monthAdapter = {
|
|
18262
|
-
normalizeFunctionValue(value) {
|
|
18263
|
-
const date = toNumber(value, DEFAULT_LOCALE);
|
|
18264
|
-
return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
|
|
18265
|
-
},
|
|
18266
|
-
getFormat() {
|
|
18267
|
-
return "mmmm yyyy";
|
|
18268
|
-
},
|
|
18269
|
-
formatValue(normalizedValue, locale) {
|
|
18270
|
-
locale = locale ?? DEFAULT_LOCALE;
|
|
18271
|
-
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18272
|
-
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
18273
|
-
},
|
|
18274
|
-
toCellValue(normalizedValue) {
|
|
18275
|
-
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18276
|
-
},
|
|
18277
|
-
};
|
|
18278
|
-
/**
|
|
18279
|
-
* normalizes month number
|
|
18280
|
-
*/
|
|
18281
|
-
const monthNumberAdapter = {
|
|
18282
|
-
normalizeFunctionValue(value) {
|
|
18283
|
-
const month = toNumber(value, DEFAULT_LOCALE);
|
|
18284
|
-
if (month < 1 || month > 12) {
|
|
18285
|
-
throw new EvaluationError(_t("%s is not a valid month (it should be a number between 1 and 12)", month));
|
|
18286
|
-
}
|
|
18287
|
-
return month;
|
|
18288
|
-
},
|
|
18289
|
-
getFormat() {
|
|
18290
|
-
return "0";
|
|
18291
|
-
},
|
|
18292
|
-
formatValue(normalizedValue, locale) {
|
|
18293
|
-
locale = locale ?? DEFAULT_LOCALE;
|
|
18294
|
-
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18295
|
-
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
18296
|
-
},
|
|
18297
|
-
toCellValue(normalizedValue) {
|
|
18298
|
-
return MONTHS[toNumber(normalizedValue, DEFAULT_LOCALE) - 1].toString();
|
|
18299
|
-
},
|
|
18300
|
-
};
|
|
18301
|
-
/**
|
|
18302
|
-
* normalized quarter value is "quarter/year"
|
|
18303
|
-
* e.g. "1/2020" for Q1 2020
|
|
18304
|
-
*/
|
|
18305
|
-
const quarterAdapter = {
|
|
18306
|
-
normalizeFunctionValue(value) {
|
|
18307
|
-
const [quarter, year] = value.split("/");
|
|
18308
|
-
return `${quarter}/${year}`;
|
|
18309
|
-
},
|
|
18310
|
-
getFormat() {
|
|
18311
|
-
return undefined;
|
|
18312
|
-
},
|
|
18313
|
-
formatValue(normalizedValue) {
|
|
18314
|
-
const [quarter, year] = normalizedValue.split("/");
|
|
18315
|
-
return _t("Q%(quarter)s %(year)s", { quarter, year });
|
|
18316
|
-
},
|
|
18317
|
-
toCellValue(normalizedValue) {
|
|
18318
|
-
return this.formatValue(normalizedValue);
|
|
18319
|
-
},
|
|
18320
|
-
};
|
|
18321
|
-
/**
|
|
18322
|
-
* normalizes quarter number
|
|
18323
|
-
*/
|
|
18324
|
-
const quarterNumberAdapter = {
|
|
18325
|
-
normalizeFunctionValue(value) {
|
|
18326
|
-
const quarter = toNumber(value, DEFAULT_LOCALE);
|
|
18327
|
-
if (quarter < 1 || quarter > 4) {
|
|
18328
|
-
throw new EvaluationError(_t("%s is not a valid quarter (it should be a number between 1 and 4)", quarter));
|
|
18329
|
-
}
|
|
18330
|
-
return quarter;
|
|
18331
|
-
},
|
|
18332
|
-
getFormat() {
|
|
18333
|
-
return "0";
|
|
18334
|
-
},
|
|
18335
|
-
formatValue(normalizedValue, locale) {
|
|
18336
|
-
locale = locale ?? DEFAULT_LOCALE;
|
|
18337
|
-
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18338
|
-
return formatValue(value, { locale, format: this.getFormat(locale) });
|
|
18339
|
-
},
|
|
18340
|
-
toCellValue(normalizedValue) {
|
|
18341
|
-
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18342
|
-
},
|
|
18343
|
-
};
|
|
18344
|
-
const yearAdapter = {
|
|
18345
|
-
normalizeFunctionValue(value) {
|
|
18346
|
-
return toNumber(value, DEFAULT_LOCALE);
|
|
18347
|
-
},
|
|
18348
|
-
getFormat() {
|
|
18349
|
-
return "0";
|
|
18350
|
-
},
|
|
18351
|
-
formatValue(normalizedValue, locale) {
|
|
18352
|
-
locale = locale ?? DEFAULT_LOCALE;
|
|
18353
|
-
return formatValue(normalizedValue, { locale, format: "0" });
|
|
18354
|
-
},
|
|
18355
|
-
toCellValue(normalizedValue) {
|
|
18356
|
-
return toNumber(normalizedValue, DEFAULT_LOCALE);
|
|
18357
|
-
},
|
|
18358
|
-
};
|
|
18359
|
-
pivotTimeAdapterRegistry
|
|
18360
|
-
.add("day", dayAdapter)
|
|
18361
|
-
.add("week", weekAdapter)
|
|
18362
|
-
.add("month", monthAdapter)
|
|
18363
|
-
.add("quarter", quarterAdapter)
|
|
18364
|
-
.add("year", yearAdapter)
|
|
18365
|
-
.add("day_of_month", dayOfMonthAdapter)
|
|
18366
|
-
.add("iso_week_number", isoWeekNumberAdapter)
|
|
18367
|
-
.add("month_number", monthNumberAdapter)
|
|
18368
|
-
.add("quarter_number", quarterNumberAdapter)
|
|
18369
|
-
.add("year_number", yearAdapter);
|
|
18370
|
-
|
|
18371
|
-
const AGGREGATOR_NAMES = {
|
|
18372
|
-
count: _t("Count"),
|
|
18373
|
-
count_distinct: _t("Count Distinct"),
|
|
18374
|
-
bool_and: _t("Boolean And"),
|
|
18375
|
-
bool_or: _t("Boolean Or"),
|
|
18376
|
-
max: _t("Maximum"),
|
|
18377
|
-
min: _t("Minimum"),
|
|
18378
|
-
avg: _t("Average"),
|
|
18379
|
-
sum: _t("Sum"),
|
|
18380
|
-
};
|
|
18381
|
-
const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
|
|
18382
|
-
const AGGREGATORS_BY_FIELD_TYPE = {
|
|
18383
|
-
integer: NUMBER_CHAR_AGGREGATORS,
|
|
18384
|
-
char: NUMBER_CHAR_AGGREGATORS,
|
|
18385
|
-
boolean: ["count_distinct", "count", "bool_and", "bool_or"],
|
|
18386
|
-
};
|
|
18387
|
-
const AGGREGATORS = {};
|
|
18388
|
-
for (const type in AGGREGATORS_BY_FIELD_TYPE) {
|
|
18389
|
-
AGGREGATORS[type] = {};
|
|
18390
|
-
for (const aggregator of AGGREGATORS_BY_FIELD_TYPE[type]) {
|
|
18391
|
-
AGGREGATORS[type][aggregator] = AGGREGATOR_NAMES[aggregator];
|
|
18392
|
-
}
|
|
18393
|
-
}
|
|
18394
|
-
const AGGREGATORS_FN = {
|
|
18395
|
-
count: {
|
|
18396
|
-
fn: (args) => countAny([args]),
|
|
18397
|
-
format: () => "0",
|
|
18398
|
-
},
|
|
18399
|
-
count_distinct: {
|
|
18400
|
-
fn: (args) => countUnique([args]),
|
|
18401
|
-
format: () => "0",
|
|
18402
|
-
},
|
|
18403
|
-
bool_and: {
|
|
18404
|
-
fn: (args) => boolAnd([args]).result,
|
|
18405
|
-
format: () => undefined,
|
|
18406
|
-
},
|
|
18407
|
-
bool_or: {
|
|
18408
|
-
fn: (args) => boolOr([args]).result,
|
|
18409
|
-
format: () => undefined,
|
|
18410
|
-
},
|
|
18411
|
-
max: {
|
|
18412
|
-
fn: (args, locale) => max([args], locale),
|
|
18413
|
-
format: inferFormat,
|
|
18414
|
-
},
|
|
18415
|
-
min: {
|
|
18416
|
-
fn: (args, locale) => min([args], locale),
|
|
18417
|
-
format: inferFormat,
|
|
18418
|
-
},
|
|
18419
|
-
avg: {
|
|
18420
|
-
fn: (args, locale) => average([args], locale),
|
|
18421
|
-
format: inferFormat,
|
|
18422
|
-
},
|
|
18423
|
-
sum: {
|
|
18424
|
-
fn: (args, locale) => sum([args], locale),
|
|
18425
|
-
format: inferFormat,
|
|
18426
|
-
},
|
|
18427
|
-
};
|
|
18428
|
-
/**
|
|
18429
|
-
* Build a pivot formula expression
|
|
18430
|
-
*/
|
|
18431
|
-
function makePivotFormula(formula, args) {
|
|
18432
|
-
return `=${formula}(${args
|
|
18433
|
-
.map((arg) => {
|
|
18434
|
-
const stringIsNumber = typeof arg == "string" && !isNaN(Number(arg)) && Number(arg).toString() === arg;
|
|
18435
|
-
const convertToNumber = typeof arg == "number" || stringIsNumber;
|
|
18436
|
-
return convertToNumber ? `${arg}` : `"${arg.toString().replace(/"/g, '\\"')}"`;
|
|
18437
|
-
})
|
|
18438
|
-
.join(",")})`;
|
|
18439
|
-
}
|
|
18440
|
-
/**
|
|
18441
|
-
* Given an object of form {"1": {...}, "2": {...}, ...} get the maximum ID used
|
|
18442
|
-
* in this object
|
|
18443
|
-
* If the object has no keys, return 0
|
|
18444
|
-
*
|
|
18445
|
-
*/
|
|
18446
|
-
function getMaxObjectId(o) {
|
|
18447
|
-
const keys = Object.keys(o);
|
|
18448
|
-
if (!keys.length) {
|
|
18449
|
-
return 0;
|
|
18450
|
-
}
|
|
18451
|
-
const nums = keys.map((id) => parseInt(id, 10));
|
|
18452
|
-
const max = Math.max(...nums);
|
|
18453
|
-
return max;
|
|
18454
|
-
}
|
|
18455
|
-
const ALL_PERIODS = {
|
|
18456
|
-
year: _t("Year"),
|
|
18457
|
-
quarter: _t("Quarter"),
|
|
18458
|
-
month: _t("Month"),
|
|
18459
|
-
week: _t("Week"),
|
|
18460
|
-
day: _t("Day"),
|
|
18461
|
-
year_number: _t("Year"),
|
|
18462
|
-
quarter_number: _t("Quarter"),
|
|
18463
|
-
month_number: _t("Month"),
|
|
18464
|
-
iso_week_number: _t("Week"),
|
|
18465
|
-
day_of_month: _t("Day of Month"),
|
|
18466
|
-
};
|
|
18467
|
-
const DATE_FIELDS = ["date", "datetime"];
|
|
18468
|
-
/**
|
|
18469
|
-
* Parse a dimension string into a pivot dimension definition.
|
|
18470
|
-
* e.g "create_date:month" => { name: "create_date", granularity: "month" }
|
|
18471
|
-
*/
|
|
18472
|
-
function parseDimension(dimension) {
|
|
18473
|
-
const [name, granularity] = dimension.split(":");
|
|
18474
|
-
if (granularity) {
|
|
18475
|
-
return { name, granularity };
|
|
18476
|
-
}
|
|
18477
|
-
return { name };
|
|
18478
|
-
}
|
|
18479
|
-
function isDateField(field) {
|
|
18480
|
-
return DATE_FIELDS.includes(field.type);
|
|
18481
|
-
}
|
|
18482
|
-
function toPivotDomain(domainStr) {
|
|
18483
|
-
if (domainStr.length % 2 !== 0) {
|
|
18484
|
-
throw new Error("Invalid domain: odd number of elements");
|
|
18485
|
-
}
|
|
18486
|
-
const domain = [];
|
|
18487
|
-
for (let i = 0; i < domainStr.length - 1; i += 2) {
|
|
18488
|
-
domain.push({ field: domainStr[i], value: domainStr[i + 1] });
|
|
18489
|
-
}
|
|
18490
|
-
return domain;
|
|
18491
|
-
}
|
|
18492
|
-
function flatPivotDomain(domain) {
|
|
18493
|
-
return domain.flatMap((arg) => [arg.field, arg.value]);
|
|
18494
|
-
}
|
|
18495
|
-
/**
|
|
18496
|
-
* Parses the value defining a pivot group in a PIVOT formula
|
|
18497
|
-
* e.g. given the following formula PIVOT.VALUE("1", "stage_id", "42", "status", "won"),
|
|
18498
|
-
* the two group values are "42" and "won".
|
|
18499
|
-
*/
|
|
18500
|
-
function toNormalizedPivotValue(dimension, groupValue) {
|
|
18501
|
-
if (groupValue === null || groupValue === "null") {
|
|
18502
|
-
return null;
|
|
18503
|
-
}
|
|
18504
|
-
const groupValueString = typeof groupValue === "boolean"
|
|
18505
|
-
? toString(groupValue).toLocaleLowerCase()
|
|
18506
|
-
: toString(groupValue);
|
|
18507
|
-
if (!pivotNormalizationValueRegistry.contains(dimension.type)) {
|
|
18508
|
-
throw new EvaluationError(_t("Field %(field)s is not supported because of its type (%(type)s)", {
|
|
18509
|
-
field: dimension.displayName,
|
|
18510
|
-
type: dimension.type,
|
|
18511
|
-
}));
|
|
18512
|
-
}
|
|
18513
|
-
// represents a field which is not set (=False server side)
|
|
18514
|
-
if (groupValueString === "false") {
|
|
18515
|
-
return false;
|
|
18516
|
-
}
|
|
18517
|
-
const normalizer = pivotNormalizationValueRegistry.get(dimension.type);
|
|
18518
|
-
return normalizer(groupValueString, dimension.granularity);
|
|
18519
|
-
}
|
|
18520
|
-
function normalizeDateTime(value, granularity) {
|
|
18521
|
-
if (!granularity) {
|
|
18522
|
-
throw "";
|
|
18523
|
-
}
|
|
18524
|
-
return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
|
|
18525
|
-
}
|
|
18526
|
-
const pivotNormalizationValueRegistry = new Registry();
|
|
18527
|
-
pivotNormalizationValueRegistry
|
|
18528
|
-
.add("date", normalizeDateTime)
|
|
18529
|
-
.add("datetime", normalizeDateTime)
|
|
18530
|
-
.add("integer", (value) => toNumber(value, DEFAULT_LOCALE))
|
|
18531
|
-
.add("boolean", (value) => toBoolean(value))
|
|
18532
|
-
.add("char", (value) => toString(value));
|
|
18533
|
-
|
|
18534
18619
|
/**
|
|
18535
18620
|
* Get the pivot ID from the formula pivot ID.
|
|
18536
18621
|
*/
|
|
@@ -19107,14 +19192,10 @@ const PIVOT = {
|
|
|
19107
19192
|
result[col].push({ value: "" });
|
|
19108
19193
|
break;
|
|
19109
19194
|
case "HEADER":
|
|
19110
|
-
|
|
19111
|
-
|
|
19112
|
-
|
|
19113
|
-
|
|
19114
|
-
}
|
|
19115
|
-
else {
|
|
19116
|
-
result[col].push(pivot.getPivotHeaderValueAndFormat(domain));
|
|
19117
|
-
}
|
|
19195
|
+
result[col].push(pivot.getPivotHeaderValueAndFormat(pivotCell.domain));
|
|
19196
|
+
break;
|
|
19197
|
+
case "MEASURE_HEADER":
|
|
19198
|
+
result[col].push(pivot.getPivotMeasureValue(pivotCell.measure, pivotCell.domain));
|
|
19118
19199
|
break;
|
|
19119
19200
|
case "VALUE":
|
|
19120
19201
|
result[col].push(pivot.getPivotCellValueAndFormat(pivotCell.measure, pivotCell.domain));
|
|
@@ -20153,6 +20234,8 @@ const MODIFIER_KEYS = ["Shift", "Control", "Alt", "Meta"];
|
|
|
20153
20234
|
* a child element.
|
|
20154
20235
|
*/
|
|
20155
20236
|
function isChildEvent(parent, ev) {
|
|
20237
|
+
if (!parent)
|
|
20238
|
+
return false;
|
|
20156
20239
|
return !!ev.target && parent.contains(ev.target);
|
|
20157
20240
|
}
|
|
20158
20241
|
function gridOverlayPosition() {
|
|
@@ -22692,7 +22775,8 @@ function truncateLabel(label) {
|
|
|
22692
22775
|
/**
|
|
22693
22776
|
* Get a default chart js configuration
|
|
22694
22777
|
*/
|
|
22695
|
-
function getDefaultChartJsRuntime(chart, labels, fontColor,
|
|
22778
|
+
function getDefaultChartJsRuntime(chart, labels, fontColor, args) {
|
|
22779
|
+
const { format, locale, truncateLabels, horizontalChart } = args;
|
|
22696
22780
|
const chartTitle = chart.title.text ? chart.title : { ...chart.title, content: "" };
|
|
22697
22781
|
const options = {
|
|
22698
22782
|
// https://www.chartjs.org/docs/latest/general/responsive.html
|
|
@@ -22736,8 +22820,11 @@ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, tr
|
|
|
22736
22820
|
callbacks: {
|
|
22737
22821
|
label: function (tooltipItem) {
|
|
22738
22822
|
const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
|
|
22739
|
-
// tooltipItem.parsed
|
|
22740
|
-
|
|
22823
|
+
// tooltipItem.parsed can be an object or a number for pie charts
|
|
22824
|
+
let yLabel = horizontalChart ? tooltipItem.parsed.x : tooltipItem.parsed.y;
|
|
22825
|
+
if (!yLabel) {
|
|
22826
|
+
yLabel = tooltipItem.parsed;
|
|
22827
|
+
}
|
|
22741
22828
|
const toolTipFormat = !format && Math.abs(yLabel) >= 1000 ? "#,##" : format;
|
|
22742
22829
|
const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
|
|
22743
22830
|
return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
|
|
@@ -22911,6 +22998,7 @@ class BarChart extends AbstractChart {
|
|
|
22911
22998
|
dataSetsHaveTitle;
|
|
22912
22999
|
dataSetDesign;
|
|
22913
23000
|
axesDesign;
|
|
23001
|
+
horizontal;
|
|
22914
23002
|
constructor(definition, sheetId, getters) {
|
|
22915
23003
|
super(definition, sheetId, getters);
|
|
22916
23004
|
this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
|
|
@@ -22922,6 +23010,7 @@ class BarChart extends AbstractChart {
|
|
|
22922
23010
|
this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
|
|
22923
23011
|
this.dataSetDesign = definition.dataSets;
|
|
22924
23012
|
this.axesDesign = definition.axesDesign;
|
|
23013
|
+
this.horizontal = definition.horizontal;
|
|
22925
23014
|
}
|
|
22926
23015
|
static transformDefinition(definition, executed) {
|
|
22927
23016
|
return transformChartDefinitionWithDataSetsWithZone(definition, executed);
|
|
@@ -22993,6 +23082,7 @@ class BarChart extends AbstractChart {
|
|
|
22993
23082
|
stacked: this.stacked,
|
|
22994
23083
|
aggregated: this.aggregated,
|
|
22995
23084
|
axesDesign: this.axesDesign,
|
|
23085
|
+
horizontal: this.horizontal,
|
|
22996
23086
|
};
|
|
22997
23087
|
}
|
|
22998
23088
|
getDefinitionForExcel() {
|
|
@@ -23024,7 +23114,10 @@ class BarChart extends AbstractChart {
|
|
|
23024
23114
|
}
|
|
23025
23115
|
function getBarConfiguration(chart, labels, localeFormat) {
|
|
23026
23116
|
const fontColor = chartFontColor(chart.background);
|
|
23027
|
-
const config = getDefaultChartJsRuntime(chart, labels, fontColor,
|
|
23117
|
+
const config = getDefaultChartJsRuntime(chart, labels, fontColor, {
|
|
23118
|
+
...localeFormat,
|
|
23119
|
+
horizontalChart: chart.horizontal,
|
|
23120
|
+
});
|
|
23028
23121
|
const legend = {
|
|
23029
23122
|
labels: { color: fontColor },
|
|
23030
23123
|
};
|
|
@@ -23038,16 +23131,10 @@ function getBarConfiguration(chart, labels, localeFormat) {
|
|
|
23038
23131
|
config.options.layout = {
|
|
23039
23132
|
padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
|
|
23040
23133
|
};
|
|
23041
|
-
config.options.
|
|
23042
|
-
|
|
23043
|
-
|
|
23044
|
-
|
|
23045
|
-
color: fontColor,
|
|
23046
|
-
},
|
|
23047
|
-
title: getChartAxisTitleRuntime(chart.axesDesign?.x),
|
|
23048
|
-
},
|
|
23049
|
-
};
|
|
23050
|
-
const yAxis = {
|
|
23134
|
+
config.options.indexAxis = chart.horizontal ? "y" : "x";
|
|
23135
|
+
config.options.scales = {};
|
|
23136
|
+
const labelsAxis = { ticks: { padding: 5, color: fontColor } };
|
|
23137
|
+
const valuesAxis = {
|
|
23051
23138
|
beginAtZero: true, // the origin of the y axis is always zero
|
|
23052
23139
|
ticks: {
|
|
23053
23140
|
color: fontColor,
|
|
@@ -23063,7 +23150,10 @@ function getBarConfiguration(chart, labels, localeFormat) {
|
|
|
23063
23150
|
},
|
|
23064
23151
|
},
|
|
23065
23152
|
};
|
|
23153
|
+
const xAxis = chart.horizontal ? valuesAxis : labelsAxis;
|
|
23154
|
+
const yAxis = chart.horizontal ? labelsAxis : valuesAxis;
|
|
23066
23155
|
const { useLeftAxis, useRightAxis } = getDefinedAxis(chart.getDefinition());
|
|
23156
|
+
config.options.scales.x = { ...xAxis, title: getChartAxisTitleRuntime(chart.axesDesign?.x) };
|
|
23067
23157
|
if (useLeftAxis) {
|
|
23068
23158
|
config.options.scales.y = {
|
|
23069
23159
|
...yAxis,
|
|
@@ -23130,7 +23220,7 @@ function createBarChartRuntime(chart, getters) {
|
|
|
23130
23220
|
const label = definition.dataSets[index].label;
|
|
23131
23221
|
dataset.label = label;
|
|
23132
23222
|
}
|
|
23133
|
-
if (definition.dataSets?.[index]?.yAxisId) {
|
|
23223
|
+
if (definition.dataSets?.[index]?.yAxisId && !chart.horizontal) {
|
|
23134
23224
|
dataset["yAxisID"] = definition.dataSets[index].yAxisId;
|
|
23135
23225
|
}
|
|
23136
23226
|
}
|
|
@@ -24121,6 +24211,7 @@ class PieChart extends AbstractChart {
|
|
|
24121
24211
|
type = "pie";
|
|
24122
24212
|
aggregated;
|
|
24123
24213
|
dataSetsHaveTitle;
|
|
24214
|
+
isDoughnut;
|
|
24124
24215
|
constructor(definition, sheetId, getters) {
|
|
24125
24216
|
super(definition, sheetId, getters);
|
|
24126
24217
|
this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
|
|
@@ -24129,6 +24220,7 @@ class PieChart extends AbstractChart {
|
|
|
24129
24220
|
this.legendPosition = definition.legendPosition;
|
|
24130
24221
|
this.aggregated = definition.aggregated;
|
|
24131
24222
|
this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
|
|
24223
|
+
this.isDoughnut = definition.isDoughnut;
|
|
24132
24224
|
}
|
|
24133
24225
|
static transformDefinition(definition, executed) {
|
|
24134
24226
|
return transformChartDefinitionWithDataSetsWithZone(definition, executed);
|
|
@@ -24146,6 +24238,7 @@ class PieChart extends AbstractChart {
|
|
|
24146
24238
|
type: "pie",
|
|
24147
24239
|
labelRange: context.auxiliaryRange || undefined,
|
|
24148
24240
|
aggregated: context.aggregated ?? false,
|
|
24241
|
+
isDoughnut: false,
|
|
24149
24242
|
};
|
|
24150
24243
|
}
|
|
24151
24244
|
getDefinition() {
|
|
@@ -24176,6 +24269,7 @@ class PieChart extends AbstractChart {
|
|
|
24176
24269
|
: undefined,
|
|
24177
24270
|
title: this.title,
|
|
24178
24271
|
aggregated: this.aggregated,
|
|
24272
|
+
isDoughnut: this.isDoughnut,
|
|
24179
24273
|
};
|
|
24180
24274
|
}
|
|
24181
24275
|
copyForSheetId(sheetId) {
|
|
@@ -24310,6 +24404,141 @@ function createPieChartRuntime(chart, getters) {
|
|
|
24310
24404
|
};
|
|
24311
24405
|
config.data.datasets.push(dataset);
|
|
24312
24406
|
}
|
|
24407
|
+
if (chart.isDoughnut) {
|
|
24408
|
+
config.type = "doughnut";
|
|
24409
|
+
}
|
|
24410
|
+
return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
|
|
24411
|
+
}
|
|
24412
|
+
|
|
24413
|
+
class PyramidChart extends AbstractChart {
|
|
24414
|
+
dataSets;
|
|
24415
|
+
labelRange;
|
|
24416
|
+
background;
|
|
24417
|
+
legendPosition;
|
|
24418
|
+
aggregated;
|
|
24419
|
+
type = "pyramid";
|
|
24420
|
+
dataSetsHaveTitle;
|
|
24421
|
+
dataSetDesign;
|
|
24422
|
+
axesDesign;
|
|
24423
|
+
horizontal = true;
|
|
24424
|
+
stacked = true;
|
|
24425
|
+
constructor(definition, sheetId, getters) {
|
|
24426
|
+
super(definition, sheetId, getters);
|
|
24427
|
+
this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle).slice(0, 2);
|
|
24428
|
+
this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
|
|
24429
|
+
this.background = definition.background;
|
|
24430
|
+
this.legendPosition = definition.legendPosition;
|
|
24431
|
+
this.aggregated = definition.aggregated;
|
|
24432
|
+
this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
|
|
24433
|
+
this.dataSetDesign = definition.dataSets;
|
|
24434
|
+
this.axesDesign = definition.axesDesign;
|
|
24435
|
+
}
|
|
24436
|
+
static transformDefinition(definition, executed) {
|
|
24437
|
+
return transformChartDefinitionWithDataSetsWithZone(definition, executed);
|
|
24438
|
+
}
|
|
24439
|
+
static validateChartDefinition(validator, definition) {
|
|
24440
|
+
return validator.checkValidations(definition, checkDataset, checkLabelRange);
|
|
24441
|
+
}
|
|
24442
|
+
static getDefinitionFromContextCreation(context) {
|
|
24443
|
+
return {
|
|
24444
|
+
background: context.background,
|
|
24445
|
+
dataSets: context.range ?? [],
|
|
24446
|
+
dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
|
|
24447
|
+
aggregated: context.aggregated ?? false,
|
|
24448
|
+
legendPosition: context.legendPosition ?? "top",
|
|
24449
|
+
title: context.title || { text: "" },
|
|
24450
|
+
type: "pyramid",
|
|
24451
|
+
labelRange: context.auxiliaryRange || undefined,
|
|
24452
|
+
axesDesign: context.axesDesign,
|
|
24453
|
+
horizontal: true,
|
|
24454
|
+
stacked: true,
|
|
24455
|
+
};
|
|
24456
|
+
}
|
|
24457
|
+
getContextCreation() {
|
|
24458
|
+
const range = [];
|
|
24459
|
+
for (const [i, dataSet] of this.dataSets.entries()) {
|
|
24460
|
+
range.push({
|
|
24461
|
+
...this.dataSetDesign?.[i],
|
|
24462
|
+
dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
|
|
24463
|
+
});
|
|
24464
|
+
}
|
|
24465
|
+
return {
|
|
24466
|
+
...this,
|
|
24467
|
+
range,
|
|
24468
|
+
auxiliaryRange: this.labelRange
|
|
24469
|
+
? this.getters.getRangeString(this.labelRange, this.sheetId)
|
|
24470
|
+
: undefined,
|
|
24471
|
+
};
|
|
24472
|
+
}
|
|
24473
|
+
copyForSheetId(sheetId) {
|
|
24474
|
+
const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
|
|
24475
|
+
const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
|
|
24476
|
+
const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
|
|
24477
|
+
return new PyramidChart(definition, sheetId, this.getters);
|
|
24478
|
+
}
|
|
24479
|
+
copyInSheetId(sheetId) {
|
|
24480
|
+
const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
|
|
24481
|
+
return new PyramidChart(definition, sheetId, this.getters);
|
|
24482
|
+
}
|
|
24483
|
+
getDefinition() {
|
|
24484
|
+
return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
|
|
24485
|
+
}
|
|
24486
|
+
getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
|
|
24487
|
+
const ranges = [];
|
|
24488
|
+
for (const [i, dataSet] of dataSets.entries()) {
|
|
24489
|
+
ranges.push({
|
|
24490
|
+
...this.dataSetDesign?.[i],
|
|
24491
|
+
dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
|
|
24492
|
+
});
|
|
24493
|
+
}
|
|
24494
|
+
return {
|
|
24495
|
+
type: "pyramid",
|
|
24496
|
+
dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
|
|
24497
|
+
background: this.background,
|
|
24498
|
+
dataSets: ranges,
|
|
24499
|
+
legendPosition: this.legendPosition,
|
|
24500
|
+
labelRange: labelRange
|
|
24501
|
+
? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
|
|
24502
|
+
: undefined,
|
|
24503
|
+
title: this.title,
|
|
24504
|
+
aggregated: this.aggregated,
|
|
24505
|
+
axesDesign: this.axesDesign,
|
|
24506
|
+
horizontal: true,
|
|
24507
|
+
stacked: true,
|
|
24508
|
+
};
|
|
24509
|
+
}
|
|
24510
|
+
getDefinitionForExcel() {
|
|
24511
|
+
return undefined;
|
|
24512
|
+
}
|
|
24513
|
+
updateRanges(applyChange) {
|
|
24514
|
+
const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
|
|
24515
|
+
if (!isStale) {
|
|
24516
|
+
return this;
|
|
24517
|
+
}
|
|
24518
|
+
const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
|
|
24519
|
+
return new PyramidChart(definition, this.sheetId, this.getters);
|
|
24520
|
+
}
|
|
24521
|
+
}
|
|
24522
|
+
function createPyramidChartRuntime(chart, getters) {
|
|
24523
|
+
const barDef = { ...chart.getDefinition(), type: "bar" };
|
|
24524
|
+
const barChart = new BarChart(barDef, chart.sheetId, getters);
|
|
24525
|
+
const barRuntime = createBarChartRuntime(barChart, getters);
|
|
24526
|
+
const config = barRuntime.chartJsConfig;
|
|
24527
|
+
let datasets = config.data?.datasets;
|
|
24528
|
+
if (datasets && datasets[0]) {
|
|
24529
|
+
datasets[0].data = datasets[0].data.map((value) => (value > 0 ? value : 0));
|
|
24530
|
+
}
|
|
24531
|
+
if (datasets && datasets[1]) {
|
|
24532
|
+
datasets[1].data = datasets[1].data.map((value) => (value > 0 ? -value : 0));
|
|
24533
|
+
}
|
|
24534
|
+
const scales = config.options.scales;
|
|
24535
|
+
const scalesXCallback = scales.x.ticks.callback;
|
|
24536
|
+
scales.x.ticks.callback = (value) => scalesXCallback(Math.abs(value));
|
|
24537
|
+
const tooltipLabelCallback = config.options.plugins.tooltip.callbacks.label;
|
|
24538
|
+
config.options.plugins.tooltip.callbacks.label = (item) => {
|
|
24539
|
+
const tooltipItem = { ...item, parsed: { y: item.parsed.y, x: Math.abs(item.parsed.x) } };
|
|
24540
|
+
return tooltipLabelCallback(tooltipItem);
|
|
24541
|
+
};
|
|
24313
24542
|
return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
|
|
24314
24543
|
}
|
|
24315
24544
|
|
|
@@ -24752,7 +24981,6 @@ chartRegistry.add("bar", {
|
|
|
24752
24981
|
validateChartDefinition: BarChart.validateChartDefinition,
|
|
24753
24982
|
transformDefinition: BarChart.transformDefinition,
|
|
24754
24983
|
getChartDefinitionFromContextCreation: BarChart.getDefinitionFromContextCreation,
|
|
24755
|
-
name: _t("Bar"),
|
|
24756
24984
|
sequence: 10,
|
|
24757
24985
|
});
|
|
24758
24986
|
chartRegistry.add("combo", {
|
|
@@ -24762,7 +24990,6 @@ chartRegistry.add("combo", {
|
|
|
24762
24990
|
validateChartDefinition: ComboChart.validateChartDefinition,
|
|
24763
24991
|
transformDefinition: ComboChart.transformDefinition,
|
|
24764
24992
|
getChartDefinitionFromContextCreation: ComboChart.getDefinitionFromContextCreation,
|
|
24765
|
-
name: _t("Combo"),
|
|
24766
24993
|
sequence: 15,
|
|
24767
24994
|
});
|
|
24768
24995
|
chartRegistry.add("line", {
|
|
@@ -24772,7 +24999,6 @@ chartRegistry.add("line", {
|
|
|
24772
24999
|
validateChartDefinition: LineChart.validateChartDefinition,
|
|
24773
25000
|
transformDefinition: LineChart.transformDefinition,
|
|
24774
25001
|
getChartDefinitionFromContextCreation: LineChart.getDefinitionFromContextCreation,
|
|
24775
|
-
name: _t("Line"),
|
|
24776
25002
|
sequence: 20,
|
|
24777
25003
|
});
|
|
24778
25004
|
chartRegistry.add("pie", {
|
|
@@ -24782,7 +25008,6 @@ chartRegistry.add("pie", {
|
|
|
24782
25008
|
validateChartDefinition: PieChart.validateChartDefinition,
|
|
24783
25009
|
transformDefinition: PieChart.transformDefinition,
|
|
24784
25010
|
getChartDefinitionFromContextCreation: PieChart.getDefinitionFromContextCreation,
|
|
24785
|
-
name: _t("Pie"),
|
|
24786
25011
|
sequence: 30,
|
|
24787
25012
|
});
|
|
24788
25013
|
chartRegistry.add("scorecard", {
|
|
@@ -24792,7 +25017,6 @@ chartRegistry.add("scorecard", {
|
|
|
24792
25017
|
validateChartDefinition: ScorecardChart$1.validateChartDefinition,
|
|
24793
25018
|
transformDefinition: ScorecardChart$1.transformDefinition,
|
|
24794
25019
|
getChartDefinitionFromContextCreation: ScorecardChart$1.getDefinitionFromContextCreation,
|
|
24795
|
-
name: _t("Scorecard"),
|
|
24796
25020
|
sequence: 40,
|
|
24797
25021
|
});
|
|
24798
25022
|
chartRegistry.add("gauge", {
|
|
@@ -24802,7 +25026,6 @@ chartRegistry.add("gauge", {
|
|
|
24802
25026
|
validateChartDefinition: GaugeChart.validateChartDefinition,
|
|
24803
25027
|
transformDefinition: GaugeChart.transformDefinition,
|
|
24804
25028
|
getChartDefinitionFromContextCreation: GaugeChart.getDefinitionFromContextCreation,
|
|
24805
|
-
name: _t("Gauge"),
|
|
24806
25029
|
sequence: 50,
|
|
24807
25030
|
});
|
|
24808
25031
|
chartRegistry.add("scatter", {
|
|
@@ -24812,7 +25035,6 @@ chartRegistry.add("scatter", {
|
|
|
24812
25035
|
validateChartDefinition: ScatterChart.validateChartDefinition,
|
|
24813
25036
|
transformDefinition: ScatterChart.transformDefinition,
|
|
24814
25037
|
getChartDefinitionFromContextCreation: ScatterChart.getDefinitionFromContextCreation,
|
|
24815
|
-
name: _t("Scatter"),
|
|
24816
25038
|
sequence: 60,
|
|
24817
25039
|
});
|
|
24818
25040
|
chartRegistry.add("waterfall", {
|
|
@@ -24822,9 +25044,17 @@ chartRegistry.add("waterfall", {
|
|
|
24822
25044
|
validateChartDefinition: WaterfallChart.validateChartDefinition,
|
|
24823
25045
|
transformDefinition: WaterfallChart.transformDefinition,
|
|
24824
25046
|
getChartDefinitionFromContextCreation: WaterfallChart.getDefinitionFromContextCreation,
|
|
24825
|
-
name: _t("Waterfall"),
|
|
24826
25047
|
sequence: 70,
|
|
24827
25048
|
});
|
|
25049
|
+
chartRegistry.add("pyramid", {
|
|
25050
|
+
match: (type) => type === "pyramid",
|
|
25051
|
+
createChart: (definition, sheetId, getters) => new PyramidChart(definition, sheetId, getters),
|
|
25052
|
+
getChartRuntime: createPyramidChartRuntime,
|
|
25053
|
+
validateChartDefinition: PyramidChart.validateChartDefinition,
|
|
25054
|
+
transformDefinition: PyramidChart.transformDefinition,
|
|
25055
|
+
getChartDefinitionFromContextCreation: PyramidChart.getDefinitionFromContextCreation,
|
|
25056
|
+
sequence: 80,
|
|
25057
|
+
});
|
|
24828
25058
|
const chartComponentRegistry = new Registry();
|
|
24829
25059
|
chartComponentRegistry.add("line", ChartJsComponent);
|
|
24830
25060
|
chartComponentRegistry.add("bar", ChartJsComponent);
|
|
@@ -24834,6 +25064,130 @@ chartComponentRegistry.add("gauge", GaugeChartComponent);
|
|
|
24834
25064
|
chartComponentRegistry.add("scatter", ChartJsComponent);
|
|
24835
25065
|
chartComponentRegistry.add("scorecard", ScorecardChart);
|
|
24836
25066
|
chartComponentRegistry.add("waterfall", ChartJsComponent);
|
|
25067
|
+
chartComponentRegistry.add("pyramid", ChartJsComponent);
|
|
25068
|
+
const chartCategories = {
|
|
25069
|
+
line: _t("Line"),
|
|
25070
|
+
column: _t("Column"),
|
|
25071
|
+
bar: _t("Bar"),
|
|
25072
|
+
pie: _t("Pie"),
|
|
25073
|
+
misc: _t("Miscellaneous"),
|
|
25074
|
+
};
|
|
25075
|
+
const chartSubtypeRegistry = new Registry();
|
|
25076
|
+
chartSubtypeRegistry
|
|
25077
|
+
.add("line", {
|
|
25078
|
+
matcher: (definition) => definition.type === "line" && !definition.stacked,
|
|
25079
|
+
displayName: _t("Line"),
|
|
25080
|
+
chartType: "line",
|
|
25081
|
+
chartSubtype: "line",
|
|
25082
|
+
subtypeDefinition: { stacked: false },
|
|
25083
|
+
category: "line",
|
|
25084
|
+
preview: "o-spreadsheet-ChartPreview.LINE_CHART",
|
|
25085
|
+
})
|
|
25086
|
+
.add("stacked_line", {
|
|
25087
|
+
matcher: (definition) => definition.type === "line" && definition.stacked,
|
|
25088
|
+
displayName: _t("Stacked Line"),
|
|
25089
|
+
chartType: "line",
|
|
25090
|
+
chartSubtype: "stacked_line",
|
|
25091
|
+
subtypeDefinition: { stacked: true },
|
|
25092
|
+
category: "line",
|
|
25093
|
+
preview: "o-spreadsheet-ChartPreview.STACKED_AREA_CHART",
|
|
25094
|
+
})
|
|
25095
|
+
.add("scatter", {
|
|
25096
|
+
displayName: _t("Scatter"),
|
|
25097
|
+
chartType: "scatter",
|
|
25098
|
+
chartSubtype: "scatter",
|
|
25099
|
+
category: "misc",
|
|
25100
|
+
preview: "o-spreadsheet-ChartPreview.SCATTER_CHART",
|
|
25101
|
+
})
|
|
25102
|
+
.add("column", {
|
|
25103
|
+
matcher: (definition) => definition.type === "bar" && !definition.stacked && !definition.horizontal,
|
|
25104
|
+
displayName: _t("Column"),
|
|
25105
|
+
chartType: "bar",
|
|
25106
|
+
chartSubtype: "column",
|
|
25107
|
+
subtypeDefinition: { stacked: false, horizontal: false },
|
|
25108
|
+
category: "column",
|
|
25109
|
+
preview: "o-spreadsheet-ChartPreview.COLUMN_CHART",
|
|
25110
|
+
})
|
|
25111
|
+
.add("stacked_column", {
|
|
25112
|
+
matcher: (definition) => definition.type === "bar" && definition.stacked && !definition.horizontal,
|
|
25113
|
+
displayName: _t("Stacked Column"),
|
|
25114
|
+
chartType: "bar",
|
|
25115
|
+
chartSubtype: "stacked_column",
|
|
25116
|
+
subtypeDefinition: { stacked: true, horizontal: false },
|
|
25117
|
+
category: "column",
|
|
25118
|
+
preview: "o-spreadsheet-ChartPreview.STACKED_COLUMN_CHART",
|
|
25119
|
+
})
|
|
25120
|
+
.add("bar", {
|
|
25121
|
+
matcher: (definition) => definition.type === "bar" && !definition.stacked && !!definition.horizontal,
|
|
25122
|
+
displayName: _t("Bar"),
|
|
25123
|
+
chartType: "bar",
|
|
25124
|
+
chartSubtype: "bar",
|
|
25125
|
+
subtypeDefinition: { horizontal: true, stacked: false },
|
|
25126
|
+
category: "bar",
|
|
25127
|
+
preview: "o-spreadsheet-ChartPreview.BAR_CHART",
|
|
25128
|
+
})
|
|
25129
|
+
.add("stacked_bar", {
|
|
25130
|
+
matcher: (definition) => definition.type === "bar" && definition.stacked && !!definition.horizontal,
|
|
25131
|
+
displayName: _t("Stacked Bar"),
|
|
25132
|
+
chartType: "bar",
|
|
25133
|
+
chartSubtype: "stacked_bar",
|
|
25134
|
+
subtypeDefinition: { horizontal: true, stacked: true },
|
|
25135
|
+
category: "bar",
|
|
25136
|
+
preview: "o-spreadsheet-ChartPreview.STACKED_BAR_CHART",
|
|
25137
|
+
})
|
|
25138
|
+
.add("combo", {
|
|
25139
|
+
displayName: _t("Combo"),
|
|
25140
|
+
chartSubtype: "combo",
|
|
25141
|
+
chartType: "combo",
|
|
25142
|
+
category: "line",
|
|
25143
|
+
preview: "o-spreadsheet-ChartPreview.COMBO_CHART",
|
|
25144
|
+
})
|
|
25145
|
+
.add("pie", {
|
|
25146
|
+
matcher: (definition) => definition.type === "pie" && !definition.isDoughnut,
|
|
25147
|
+
displayName: _t("Pie"),
|
|
25148
|
+
chartSubtype: "pie",
|
|
25149
|
+
chartType: "pie",
|
|
25150
|
+
subtypeDefinition: { isDoughnut: false },
|
|
25151
|
+
category: "pie",
|
|
25152
|
+
preview: "o-spreadsheet-ChartPreview.PIE_CHART",
|
|
25153
|
+
})
|
|
25154
|
+
.add("doughnut", {
|
|
25155
|
+
matcher: (definition) => definition.type === "pie" && !!definition.isDoughnut,
|
|
25156
|
+
displayName: _t("Doughnut"),
|
|
25157
|
+
chartSubtype: "doughnut",
|
|
25158
|
+
chartType: "pie",
|
|
25159
|
+
subtypeDefinition: { isDoughnut: true },
|
|
25160
|
+
category: "pie",
|
|
25161
|
+
preview: "o-spreadsheet-ChartPreview.DOUGHNUT_CHART",
|
|
25162
|
+
})
|
|
25163
|
+
.add("gauge", {
|
|
25164
|
+
displayName: _t("Gauge"),
|
|
25165
|
+
chartSubtype: "gauge",
|
|
25166
|
+
chartType: "gauge",
|
|
25167
|
+
category: "misc",
|
|
25168
|
+
preview: "o-spreadsheet-ChartPreview.GAUGE_CHART",
|
|
25169
|
+
})
|
|
25170
|
+
.add("scorecard", {
|
|
25171
|
+
displayName: _t("Scorecard"),
|
|
25172
|
+
chartSubtype: "scorecard",
|
|
25173
|
+
chartType: "scorecard",
|
|
25174
|
+
category: "misc",
|
|
25175
|
+
preview: "o-spreadsheet-ChartPreview.SCORECARD_CHART",
|
|
25176
|
+
})
|
|
25177
|
+
.add("waterfall", {
|
|
25178
|
+
displayName: _t("Waterfall"),
|
|
25179
|
+
chartSubtype: "waterfall",
|
|
25180
|
+
chartType: "waterfall",
|
|
25181
|
+
category: "misc",
|
|
25182
|
+
preview: "o-spreadsheet-ChartPreview.WATERFALL_CHART",
|
|
25183
|
+
})
|
|
25184
|
+
.add("pyramid", {
|
|
25185
|
+
displayName: _t("Population Pyramid"),
|
|
25186
|
+
chartSubtype: "pyramid",
|
|
25187
|
+
chartType: "pyramid",
|
|
25188
|
+
category: "misc",
|
|
25189
|
+
preview: "o-spreadsheet-ChartPreview.POPULATION_PYRAMID_CHART",
|
|
25190
|
+
});
|
|
24837
25191
|
|
|
24838
25192
|
/**
|
|
24839
25193
|
* Registry intended to support usual currencies. It is mainly used to create
|
|
@@ -26690,20 +27044,6 @@ function transformDefinition(definition, executed) {
|
|
|
26690
27044
|
}
|
|
26691
27045
|
return transformation.transformDefinition(definition, executed);
|
|
26692
27046
|
}
|
|
26693
|
-
/**
|
|
26694
|
-
* Get an empty definition based on the given context and the given type
|
|
26695
|
-
*/
|
|
26696
|
-
function getChartDefinitionFromContextCreation(context, type) {
|
|
26697
|
-
const chartClass = chartRegistry.get(type);
|
|
26698
|
-
return chartClass.getChartDefinitionFromContextCreation(context);
|
|
26699
|
-
}
|
|
26700
|
-
function getChartTypes() {
|
|
26701
|
-
const result = {};
|
|
26702
|
-
for (const key of chartRegistry.getKeys()) {
|
|
26703
|
-
result[key] = chartRegistry.get(key).name;
|
|
26704
|
-
}
|
|
26705
|
-
return result;
|
|
26706
|
-
}
|
|
26707
27047
|
/**
|
|
26708
27048
|
* Return a "smart" chart definition in the given zone. The definition is "smart" because it will
|
|
26709
27049
|
* use the best type of chart to display the data of the zone.
|
|
@@ -28163,6 +28503,40 @@ const pivotProperties = {
|
|
|
28163
28503
|
},
|
|
28164
28504
|
icon: "o-spreadsheet-Icon.PIVOT",
|
|
28165
28505
|
};
|
|
28506
|
+
const FIX_FORMULAS = {
|
|
28507
|
+
name: _t("Convert to individual formulas"),
|
|
28508
|
+
execute(env) {
|
|
28509
|
+
const position = env.model.getters.getActivePosition();
|
|
28510
|
+
const cell = env.model.getters.getCorrespondingFormulaCell(position);
|
|
28511
|
+
const pivotId = env.model.getters.getPivotIdFromPosition(position);
|
|
28512
|
+
if (!cell || !pivotId) {
|
|
28513
|
+
return;
|
|
28514
|
+
}
|
|
28515
|
+
const { sheetId, col, row } = env.model.getters.getCellPosition(cell.id);
|
|
28516
|
+
const pivot = env.model.getters.getPivot(pivotId);
|
|
28517
|
+
pivot.init();
|
|
28518
|
+
if (!pivot.isValid()) {
|
|
28519
|
+
return;
|
|
28520
|
+
}
|
|
28521
|
+
env.model.dispatch("INSERT_PIVOT", {
|
|
28522
|
+
sheetId,
|
|
28523
|
+
col,
|
|
28524
|
+
row,
|
|
28525
|
+
pivotId,
|
|
28526
|
+
table: pivot.getTableStructure().export(),
|
|
28527
|
+
});
|
|
28528
|
+
},
|
|
28529
|
+
isVisible: (env) => {
|
|
28530
|
+
const position = env.model.getters.getActivePosition();
|
|
28531
|
+
const pivotId = env.model.getters.getPivotIdFromPosition(position);
|
|
28532
|
+
if (!pivotId) {
|
|
28533
|
+
return false;
|
|
28534
|
+
}
|
|
28535
|
+
const pivot = env.model.getters.getPivot(pivotId);
|
|
28536
|
+
return pivot.isValid() && env.model.getters.isSpillPivotFormula(position);
|
|
28537
|
+
},
|
|
28538
|
+
icon: "o-spreadsheet-Icon.PIVOT",
|
|
28539
|
+
};
|
|
28166
28540
|
|
|
28167
28541
|
//------------------------------------------------------------------------------
|
|
28168
28542
|
// Context Menu Registry
|
|
@@ -28261,6 +28635,10 @@ cellMenuRegistry
|
|
|
28261
28635
|
name: INSERT_LINK_NAME,
|
|
28262
28636
|
sequence: 150,
|
|
28263
28637
|
separator: true,
|
|
28638
|
+
})
|
|
28639
|
+
.add("pivot_fix_formulas", {
|
|
28640
|
+
...FIX_FORMULAS,
|
|
28641
|
+
sequence: 155,
|
|
28264
28642
|
})
|
|
28265
28643
|
.add("pivot_properties", {
|
|
28266
28644
|
...pivotProperties,
|
|
@@ -30483,6 +30861,7 @@ class GenericChartConfigPanel extends Component {
|
|
|
30483
30861
|
});
|
|
30484
30862
|
dataSeriesRanges = [];
|
|
30485
30863
|
labelRange;
|
|
30864
|
+
chartTerms = ChartTerms;
|
|
30486
30865
|
setup() {
|
|
30487
30866
|
this.dataSeriesRanges = this.props.definition.dataSets;
|
|
30488
30867
|
this.labelRange = this.props.definition.labelRange;
|
|
@@ -30507,7 +30886,7 @@ class GenericChartConfigPanel extends Component {
|
|
|
30507
30886
|
return [
|
|
30508
30887
|
{
|
|
30509
30888
|
name: "aggregated",
|
|
30510
|
-
label:
|
|
30889
|
+
label: this.chartTerms.AggregatedChart,
|
|
30511
30890
|
value: this.props.definition.aggregated ?? false,
|
|
30512
30891
|
onChange: this.onUpdateAggregated.bind(this),
|
|
30513
30892
|
},
|
|
@@ -30583,9 +30962,6 @@ class GenericChartConfigPanel extends Component {
|
|
|
30583
30962
|
|
|
30584
30963
|
class BarConfigPanel extends GenericChartConfigPanel {
|
|
30585
30964
|
static template = "o-spreadsheet-BarConfigPanel";
|
|
30586
|
-
get stackedLabel() {
|
|
30587
|
-
return _t("Stacked barchart");
|
|
30588
|
-
}
|
|
30589
30965
|
onUpdateStacked(stacked) {
|
|
30590
30966
|
this.props.updateChart(this.props.figureId, {
|
|
30591
30967
|
stacked,
|
|
@@ -31575,6 +31951,9 @@ class ChartWithAxisDesignPanel extends Component {
|
|
|
31575
31951
|
return "left";
|
|
31576
31952
|
return dataSets[this.state.index].yAxisId === "y1" ? "right" : "left";
|
|
31577
31953
|
}
|
|
31954
|
+
get canHaveTwoVerticalAxis() {
|
|
31955
|
+
return "horizontal" in this.props.definition ? !this.props.definition.horizontal : true;
|
|
31956
|
+
}
|
|
31578
31957
|
updateDataSeriesLabel(ev) {
|
|
31579
31958
|
const label = ev.target.value;
|
|
31580
31959
|
const dataSets = this.props.definition.dataSets;
|
|
@@ -31739,19 +32118,13 @@ class LineConfigPanel extends GenericChartConfigPanel {
|
|
|
31739
32118
|
}
|
|
31740
32119
|
return false;
|
|
31741
32120
|
}
|
|
31742
|
-
get stackedLabel() {
|
|
31743
|
-
return _t("Stacked linechart");
|
|
31744
|
-
}
|
|
31745
|
-
get cumulativeLabel() {
|
|
31746
|
-
return _t("Cumulative data");
|
|
31747
|
-
}
|
|
31748
32121
|
getLabelRangeOptions() {
|
|
31749
32122
|
const options = super.getLabelRangeOptions();
|
|
31750
32123
|
if (this.canTreatLabelsAsText) {
|
|
31751
32124
|
options.push({
|
|
31752
32125
|
name: "labelsAsText",
|
|
31753
32126
|
value: this.props.definition.labelsAsText,
|
|
31754
|
-
label:
|
|
32127
|
+
label: this.chartTerms.TreatLabelsAsText,
|
|
31755
32128
|
onChange: this.onUpdateLabelsAsText.bind(this),
|
|
31756
32129
|
});
|
|
31757
32130
|
}
|
|
@@ -31818,7 +32191,7 @@ class ScatterConfigPanel extends GenericChartConfigPanel {
|
|
|
31818
32191
|
options.push({
|
|
31819
32192
|
name: "labelsAsText",
|
|
31820
32193
|
value: this.props.definition.labelsAsText,
|
|
31821
|
-
label:
|
|
32194
|
+
label: this.chartTerms.TreatLabelsAsText,
|
|
31822
32195
|
onChange: this.onUpdateLabelsAsText.bind(this),
|
|
31823
32196
|
});
|
|
31824
32197
|
}
|
|
@@ -32025,8 +32398,106 @@ chartSidePanelComponentRegistry
|
|
|
32025
32398
|
.add("waterfall", {
|
|
32026
32399
|
configuration: GenericChartConfigPanel,
|
|
32027
32400
|
design: WaterfallChartDesignPanel,
|
|
32401
|
+
})
|
|
32402
|
+
.add("pyramid", {
|
|
32403
|
+
configuration: GenericChartConfigPanel,
|
|
32404
|
+
design: ChartWithAxisDesignPanel,
|
|
32028
32405
|
});
|
|
32029
32406
|
|
|
32407
|
+
css /* scss */ `
|
|
32408
|
+
.o-section .o-type-selector {
|
|
32409
|
+
height: 30px;
|
|
32410
|
+
padding-left: 30px;
|
|
32411
|
+
}
|
|
32412
|
+
.o-type-selector-preview {
|
|
32413
|
+
left: 5px;
|
|
32414
|
+
top: 3px;
|
|
32415
|
+
.o-chart-preview {
|
|
32416
|
+
width: 24px;
|
|
32417
|
+
height: 24px;
|
|
32418
|
+
}
|
|
32419
|
+
}
|
|
32420
|
+
|
|
32421
|
+
.o-popover .o-chart-select-popover {
|
|
32422
|
+
box-sizing: border-box;
|
|
32423
|
+
background: #fff;
|
|
32424
|
+
.o-chart-type-item {
|
|
32425
|
+
cursor: pointer;
|
|
32426
|
+
padding: 3px 6px;
|
|
32427
|
+
margin: 1px 2px;
|
|
32428
|
+
&.selected,
|
|
32429
|
+
&:hover {
|
|
32430
|
+
background: #f5f5f5;
|
|
32431
|
+
border: 1px solid #ccc;
|
|
32432
|
+
padding: 2px 5px;
|
|
32433
|
+
}
|
|
32434
|
+
.o-chart-preview {
|
|
32435
|
+
width: 48px;
|
|
32436
|
+
height: 48px;
|
|
32437
|
+
}
|
|
32438
|
+
}
|
|
32439
|
+
}
|
|
32440
|
+
`;
|
|
32441
|
+
class ChartTypePicker extends Component {
|
|
32442
|
+
static template = "o-spreadsheet-ChartTypePicker";
|
|
32443
|
+
static components = { Section, Popover };
|
|
32444
|
+
static props = { figureId: String, chartPanelStore: Object };
|
|
32445
|
+
categories = chartCategories;
|
|
32446
|
+
chartTypeByCategories = {};
|
|
32447
|
+
popoverRef = useRef("popoverRef");
|
|
32448
|
+
selectRef = useRef("selectRef");
|
|
32449
|
+
state = useState({ popoverProps: undefined, popoverStyle: "" });
|
|
32450
|
+
setup() {
|
|
32451
|
+
useExternalListener(window, "pointerdown", this.onExternalClick, { capture: true });
|
|
32452
|
+
for (const subtypeProperties of chartSubtypeRegistry.getAll()) {
|
|
32453
|
+
if (this.chartTypeByCategories[subtypeProperties.category]) {
|
|
32454
|
+
this.chartTypeByCategories[subtypeProperties.category].push(subtypeProperties);
|
|
32455
|
+
}
|
|
32456
|
+
else {
|
|
32457
|
+
this.chartTypeByCategories[subtypeProperties.category] = [subtypeProperties];
|
|
32458
|
+
}
|
|
32459
|
+
}
|
|
32460
|
+
}
|
|
32461
|
+
onExternalClick(ev) {
|
|
32462
|
+
if (isChildEvent(this.popoverRef.el?.parentElement, ev) ||
|
|
32463
|
+
isChildEvent(this.selectRef.el, ev)) {
|
|
32464
|
+
return;
|
|
32465
|
+
}
|
|
32466
|
+
this.closePopover();
|
|
32467
|
+
}
|
|
32468
|
+
onTypeChange(type) {
|
|
32469
|
+
this.props.chartPanelStore.changeChartType(this.props.figureId, type);
|
|
32470
|
+
this.closePopover();
|
|
32471
|
+
}
|
|
32472
|
+
getChartDefinition(figureId) {
|
|
32473
|
+
return this.env.model.getters.getChartDefinition(figureId);
|
|
32474
|
+
}
|
|
32475
|
+
getSelectedChartSubtypeProperties() {
|
|
32476
|
+
const definition = this.getChartDefinition(this.props.figureId);
|
|
32477
|
+
const matchedChart = chartSubtypeRegistry
|
|
32478
|
+
.getAll()
|
|
32479
|
+
.find((c) => c.matcher?.(definition) || false);
|
|
32480
|
+
return matchedChart || chartSubtypeRegistry.get(definition.type);
|
|
32481
|
+
}
|
|
32482
|
+
onPointerDown(ev) {
|
|
32483
|
+
if (this.state.popoverProps) {
|
|
32484
|
+
this.closePopover();
|
|
32485
|
+
return;
|
|
32486
|
+
}
|
|
32487
|
+
const target = ev.currentTarget;
|
|
32488
|
+
const { bottom, right, width } = target.getBoundingClientRect();
|
|
32489
|
+
this.state.popoverProps = {
|
|
32490
|
+
anchorRect: { x: right, y: bottom, width: 0, height: 0 },
|
|
32491
|
+
positioning: "TopRight",
|
|
32492
|
+
verticalOffset: 0,
|
|
32493
|
+
};
|
|
32494
|
+
this.state.popoverStyle = cssPropertiesToCss({ width: `${width}px` });
|
|
32495
|
+
}
|
|
32496
|
+
closePopover() {
|
|
32497
|
+
this.state.popoverProps = undefined;
|
|
32498
|
+
}
|
|
32499
|
+
}
|
|
32500
|
+
|
|
32030
32501
|
class MainChartPanelStore extends SpreadsheetStore {
|
|
32031
32502
|
mutators = ["activatePanel", "changeChartType"];
|
|
32032
32503
|
panel = "configuration";
|
|
@@ -32034,7 +32505,7 @@ class MainChartPanelStore extends SpreadsheetStore {
|
|
|
32034
32505
|
activatePanel(panel) {
|
|
32035
32506
|
this.panel = panel;
|
|
32036
32507
|
}
|
|
32037
|
-
changeChartType(figureId,
|
|
32508
|
+
changeChartType(figureId, newDisplayType) {
|
|
32038
32509
|
this.creationContext = {
|
|
32039
32510
|
...this.creationContext,
|
|
32040
32511
|
...this.getters.getContextCreationChart(figureId),
|
|
@@ -32043,13 +32514,25 @@ class MainChartPanelStore extends SpreadsheetStore {
|
|
|
32043
32514
|
if (!sheetId) {
|
|
32044
32515
|
return;
|
|
32045
32516
|
}
|
|
32046
|
-
const definition = getChartDefinitionFromContextCreation(
|
|
32517
|
+
const definition = this.getChartDefinitionFromContextCreation(figureId, newDisplayType);
|
|
32047
32518
|
this.model.dispatch("UPDATE_CHART", {
|
|
32048
32519
|
definition,
|
|
32049
32520
|
id: figureId,
|
|
32050
32521
|
sheetId,
|
|
32051
32522
|
});
|
|
32052
32523
|
}
|
|
32524
|
+
getChartDefinitionFromContextCreation(figureId, newDisplayType) {
|
|
32525
|
+
const newChartInfo = chartSubtypeRegistry.get(newDisplayType);
|
|
32526
|
+
const ChartClass = chartRegistry.get(newChartInfo.chartType);
|
|
32527
|
+
const contextCreation = {
|
|
32528
|
+
...this.creationContext,
|
|
32529
|
+
...this.getters.getContextCreationChart(figureId),
|
|
32530
|
+
};
|
|
32531
|
+
return {
|
|
32532
|
+
...ChartClass.getChartDefinitionFromContextCreation(contextCreation),
|
|
32533
|
+
...newChartInfo.subtypeDefinition,
|
|
32534
|
+
};
|
|
32535
|
+
}
|
|
32053
32536
|
}
|
|
32054
32537
|
|
|
32055
32538
|
css /* scss */ `
|
|
@@ -32078,7 +32561,7 @@ css /* scss */ `
|
|
|
32078
32561
|
`;
|
|
32079
32562
|
class ChartPanel extends Component {
|
|
32080
32563
|
static template = "o-spreadsheet-ChartPanel";
|
|
32081
|
-
static components = { Section };
|
|
32564
|
+
static components = { Section, ChartTypePicker };
|
|
32082
32565
|
static props = { onCloseSidePanel: Function, figureId: String };
|
|
32083
32566
|
store;
|
|
32084
32567
|
get figureId() {
|
|
@@ -32138,9 +32621,6 @@ class ChartPanel extends Component {
|
|
|
32138
32621
|
getChartDefinition(figureId) {
|
|
32139
32622
|
return this.env.model.getters.getChartDefinition(figureId);
|
|
32140
32623
|
}
|
|
32141
|
-
get chartTypes() {
|
|
32142
|
-
return getChartTypes();
|
|
32143
|
-
}
|
|
32144
32624
|
}
|
|
32145
32625
|
|
|
32146
32626
|
css /* scss */ `
|
|
@@ -34145,6 +34625,10 @@ class FindAndReplaceStore extends SpreadsheetStore {
|
|
|
34145
34625
|
case "ACTIVATE_SHEET":
|
|
34146
34626
|
this.isSearchDirty = true;
|
|
34147
34627
|
break;
|
|
34628
|
+
case "REPLACE_SEARCH":
|
|
34629
|
+
for (const match of cmd.matches) {
|
|
34630
|
+
this.replaceMatch(match, cmd.searchString, cmd.replaceWith, cmd.searchOptions);
|
|
34631
|
+
}
|
|
34148
34632
|
}
|
|
34149
34633
|
}
|
|
34150
34634
|
finalize() {
|
|
@@ -34318,6 +34802,21 @@ class FindAndReplaceStore extends SpreadsheetStore {
|
|
|
34318
34802
|
searchOptions: this.searchOptions,
|
|
34319
34803
|
});
|
|
34320
34804
|
}
|
|
34805
|
+
replaceMatch(selectedMatch, searchString, replaceWith, searchOptions) {
|
|
34806
|
+
const cell = this.getters.getCell(selectedMatch);
|
|
34807
|
+
if (!cell?.content) {
|
|
34808
|
+
return;
|
|
34809
|
+
}
|
|
34810
|
+
if (cell?.isFormula && !searchOptions.searchFormulas) {
|
|
34811
|
+
return;
|
|
34812
|
+
}
|
|
34813
|
+
const searchRegex = getSearchRegex(searchString, searchOptions);
|
|
34814
|
+
const replaceRegex = new RegExp(searchRegex.source, searchRegex.flags + "g");
|
|
34815
|
+
const toReplace = this.getters.getCellText(selectedMatch, searchOptions.searchFormulas);
|
|
34816
|
+
const content = toReplace.replace(replaceRegex, replaceWith);
|
|
34817
|
+
const canonicalContent = canonicalizeNumberContent(content, this.getters.getLocale());
|
|
34818
|
+
this.model.dispatch("UPDATE_CELL", { ...selectedMatch, content: canonicalContent });
|
|
34819
|
+
}
|
|
34321
34820
|
getSearchableString(position) {
|
|
34322
34821
|
return this.getters.getCellText(position, this.searchOptions.searchFormulas);
|
|
34323
34822
|
}
|
|
@@ -35069,7 +35568,7 @@ function createMeasure(fields, measure) {
|
|
|
35069
35568
|
function createPivotDimension(fields, dimension) {
|
|
35070
35569
|
const field = fields[dimension.name];
|
|
35071
35570
|
const type = field?.type ?? "integer";
|
|
35072
|
-
const granularity = field && isDateField(field) ? dimension.granularity
|
|
35571
|
+
const granularity = field && isDateField(field) ? dimension.granularity : undefined;
|
|
35073
35572
|
return {
|
|
35074
35573
|
/**
|
|
35075
35574
|
* Get the display name of the dimension
|
|
@@ -35214,9 +35713,17 @@ class SpreadsheetPivotTable {
|
|
|
35214
35713
|
}
|
|
35215
35714
|
getPivotCell(col, row, includeTotal = true) {
|
|
35216
35715
|
const colHeadersHeight = this.columns.length;
|
|
35217
|
-
if (row
|
|
35716
|
+
if (col > 0 && row === colHeadersHeight - 1) {
|
|
35717
|
+
const domain = this.getColHeaderDomain(col, row);
|
|
35718
|
+
if (!domain) {
|
|
35719
|
+
return EMPTY_PIVOT_CELL;
|
|
35720
|
+
}
|
|
35721
|
+
const measure = domain.at(-1)?.value.toString() || "";
|
|
35722
|
+
return { type: "MEASURE_HEADER", domain: domain.slice(0, -1), measure };
|
|
35723
|
+
}
|
|
35724
|
+
else if (row <= colHeadersHeight - 1) {
|
|
35218
35725
|
const domain = this.getColHeaderDomain(col, row);
|
|
35219
|
-
return domain ? { type: "HEADER", domain } :
|
|
35726
|
+
return domain ? { type: "HEADER", domain } : EMPTY_PIVOT_CELL;
|
|
35220
35727
|
}
|
|
35221
35728
|
else if (col === 0) {
|
|
35222
35729
|
const rowIndex = row - colHeadersHeight;
|
|
@@ -35226,7 +35733,7 @@ class SpreadsheetPivotTable {
|
|
|
35226
35733
|
else {
|
|
35227
35734
|
const rowIndex = row - colHeadersHeight;
|
|
35228
35735
|
if (!includeTotal && this.isTotalRow(rowIndex)) {
|
|
35229
|
-
return
|
|
35736
|
+
return EMPTY_PIVOT_CELL;
|
|
35230
35737
|
}
|
|
35231
35738
|
const domain = [...this.getRowDomain(rowIndex), ...this.getColDomain(col)];
|
|
35232
35739
|
const measure = this.getColMeasure(col);
|
|
@@ -35280,6 +35787,7 @@ class SpreadsheetPivotTable {
|
|
|
35280
35787
|
};
|
|
35281
35788
|
}
|
|
35282
35789
|
}
|
|
35790
|
+
const EMPTY_PIVOT_CELL = { type: "EMPTY" };
|
|
35283
35791
|
|
|
35284
35792
|
/**
|
|
35285
35793
|
* This function converts a list of data entry into a spreadsheet pivot table.
|
|
@@ -35473,10 +35981,13 @@ function compareDimensionValues(dimension, a, b) {
|
|
|
35473
35981
|
}
|
|
35474
35982
|
|
|
35475
35983
|
function createDate(dimension, value, locale) {
|
|
35476
|
-
const granularity = dimension.granularity
|
|
35477
|
-
if (!(granularity in MAP_VALUE_DIMENSION_DATE)) {
|
|
35984
|
+
const granularity = dimension.granularity;
|
|
35985
|
+
if (!granularity || !(granularity in MAP_VALUE_DIMENSION_DATE)) {
|
|
35478
35986
|
throw new Error(`Unknown date granularity: ${granularity}`);
|
|
35479
35987
|
}
|
|
35988
|
+
if (value === null) {
|
|
35989
|
+
return null;
|
|
35990
|
+
}
|
|
35480
35991
|
if (!MAP_VALUE_DIMENSION_DATE[granularity].set.has(value)) {
|
|
35481
35992
|
MAP_VALUE_DIMENSION_DATE[granularity].set.add(value);
|
|
35482
35993
|
const date = toJsDate(value, locale);
|
|
@@ -35707,14 +36218,17 @@ class SpreadsheetPivot {
|
|
|
35707
36218
|
if (dimension.type === "date") {
|
|
35708
36219
|
const adapter = pivotTimeAdapter(dimension.granularity);
|
|
35709
36220
|
return {
|
|
35710
|
-
value:
|
|
36221
|
+
value: lastNode.value !== "null"
|
|
36222
|
+
? adapter.toCellValue(toNormalizedPivotValue(dimension, lastNode.value))
|
|
36223
|
+
: _t("(Undefined)"),
|
|
35711
36224
|
format: adapter.getFormat(this.getters.getLocale()),
|
|
35712
36225
|
};
|
|
35713
36226
|
}
|
|
35714
36227
|
if (!finalCell) {
|
|
35715
36228
|
return { value: "" };
|
|
35716
36229
|
}
|
|
35717
|
-
|
|
36230
|
+
// Value can be null but stringified (e.g. an empty date, as for now every date is stringified)
|
|
36231
|
+
if (finalCell.value === null || finalCell.value === `${null}`) {
|
|
35718
36232
|
return { value: _t("(Undefined)") };
|
|
35719
36233
|
}
|
|
35720
36234
|
return {
|
|
@@ -35736,10 +36250,15 @@ class SpreadsheetPivot {
|
|
|
35736
36250
|
if (!operator) {
|
|
35737
36251
|
throw new Error(`Aggregator ${aggregator} does not exist`);
|
|
35738
36252
|
}
|
|
35739
|
-
|
|
35740
|
-
|
|
35741
|
-
|
|
35742
|
-
|
|
36253
|
+
try {
|
|
36254
|
+
return {
|
|
36255
|
+
value: values.length ? operator.fn([values], this.getters.getLocale()) : "",
|
|
36256
|
+
format: operator.format(values[0]),
|
|
36257
|
+
};
|
|
36258
|
+
}
|
|
36259
|
+
catch (e) {
|
|
36260
|
+
return handleError(e, aggregator.toUpperCase());
|
|
36261
|
+
}
|
|
35743
36262
|
}
|
|
35744
36263
|
getPossibleFieldValues(dimension) {
|
|
35745
36264
|
const values = [];
|
|
@@ -51106,12 +51625,12 @@ class PivotCorePlugin extends CorePlugin {
|
|
|
51106
51625
|
for (let col = 0; col < pivotCells.length; col++) {
|
|
51107
51626
|
for (let row = 0; row < pivotCells[col].length; row++) {
|
|
51108
51627
|
const pivotCell = pivotCells[col][row];
|
|
51109
|
-
|
|
51628
|
+
this.dispatch("UPDATE_CELL", {
|
|
51110
51629
|
sheetId: position.sheetId,
|
|
51111
51630
|
col: position.col + col,
|
|
51112
51631
|
row: position.row + row,
|
|
51113
|
-
|
|
51114
|
-
|
|
51632
|
+
content: makePivotFormulaFromPivotCell(formulaId, pivotCell),
|
|
51633
|
+
});
|
|
51115
51634
|
}
|
|
51116
51635
|
}
|
|
51117
51636
|
}
|
|
@@ -51141,21 +51660,6 @@ class PivotCorePlugin extends CorePlugin {
|
|
|
51141
51660
|
});
|
|
51142
51661
|
}
|
|
51143
51662
|
}
|
|
51144
|
-
addPivotFormula(position, formulaId, pivotCell) {
|
|
51145
|
-
let content = undefined;
|
|
51146
|
-
switch (pivotCell.type) {
|
|
51147
|
-
case "HEADER":
|
|
51148
|
-
content = makePivotFormula("PIVOT.HEADER", [formulaId, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
|
|
51149
|
-
break;
|
|
51150
|
-
case "VALUE":
|
|
51151
|
-
content = makePivotFormula("PIVOT.VALUE", [formulaId, pivotCell.measure, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
|
|
51152
|
-
break;
|
|
51153
|
-
}
|
|
51154
|
-
this.dispatch("UPDATE_CELL", {
|
|
51155
|
-
...position,
|
|
51156
|
-
content,
|
|
51157
|
-
});
|
|
51158
|
-
}
|
|
51159
51663
|
getPivotCore(pivotId) {
|
|
51160
51664
|
const pivot = this.pivots[pivotId];
|
|
51161
51665
|
if (!pivot) {
|
|
@@ -52820,7 +53324,6 @@ class Evaluator {
|
|
|
52820
53324
|
if (!this.blockedArrayFormulas.has(position)) {
|
|
52821
53325
|
this.invalidateSpreading(position);
|
|
52822
53326
|
}
|
|
52823
|
-
this.spreadingRelations.removeNode(position);
|
|
52824
53327
|
const cell = this.getters.getCell(position);
|
|
52825
53328
|
if (cell === undefined) {
|
|
52826
53329
|
return EMPTY_CELL;
|
|
@@ -52862,6 +53365,7 @@ class Evaluator {
|
|
|
52862
53365
|
this.assertSheetHasEnoughSpaceToSpreadFormulaResult(formulaPosition, formulaReturn);
|
|
52863
53366
|
const nbColumns = formulaReturn.length;
|
|
52864
53367
|
const nbRows = formulaReturn[0].length;
|
|
53368
|
+
this.spreadingRelations.removeNode(formulaPosition);
|
|
52865
53369
|
forEachSpreadPositionInMatrix(nbColumns, nbRows, this.updateSpreadRelation(formulaPosition));
|
|
52866
53370
|
this.assertNoMergedCellsInSpreadZone(formulaPosition, formulaReturn);
|
|
52867
53371
|
forEachSpreadPositionInMatrix(nbColumns, nbRows, this.checkCollision(formulaPosition));
|
|
@@ -54389,9 +54893,10 @@ class PivotUIPlugin extends UIPlugin {
|
|
|
54389
54893
|
"getPivot",
|
|
54390
54894
|
"getFirstPivotFunction",
|
|
54391
54895
|
"getPivotIdFromPosition",
|
|
54392
|
-
"
|
|
54896
|
+
"getPivotCellFromPosition",
|
|
54393
54897
|
"isPivotUnused",
|
|
54394
54898
|
"areDomainArgsFieldsValid",
|
|
54899
|
+
"isSpillPivotFormula",
|
|
54395
54900
|
];
|
|
54396
54901
|
pivots = {};
|
|
54397
54902
|
unusedPivots;
|
|
@@ -54470,6 +54975,14 @@ class PivotUIPlugin extends UIPlugin {
|
|
|
54470
54975
|
}
|
|
54471
54976
|
return undefined;
|
|
54472
54977
|
}
|
|
54978
|
+
isSpillPivotFormula(position) {
|
|
54979
|
+
const cell = this.getters.getCorrespondingFormulaCell(position);
|
|
54980
|
+
if (cell && cell.isFormula) {
|
|
54981
|
+
const pivotFunction = this.getFirstPivotFunction(cell.compiledFormula.tokens);
|
|
54982
|
+
return pivotFunction?.functionName === "PIVOT";
|
|
54983
|
+
}
|
|
54984
|
+
return false;
|
|
54985
|
+
}
|
|
54473
54986
|
getFirstPivotFunction(tokens) {
|
|
54474
54987
|
const pivotFunction = getFirstPivotFunction(tokens);
|
|
54475
54988
|
if (!pivotFunction) {
|
|
@@ -54503,29 +55016,29 @@ class PivotUIPlugin extends UIPlugin {
|
|
|
54503
55016
|
* If the cell is the result of PIVOT, the result is the domain of the cell
|
|
54504
55017
|
* as if it was the individual pivot formula
|
|
54505
55018
|
*/
|
|
54506
|
-
|
|
55019
|
+
getPivotCellFromPosition(position) {
|
|
54507
55020
|
const cell = this.getters.getCorrespondingFormulaCell(position);
|
|
54508
55021
|
if (!cell || !cell.isFormula || getNumberOfPivotFunctions(cell.compiledFormula.tokens) === 0) {
|
|
54509
|
-
return
|
|
55022
|
+
return EMPTY_PIVOT_CELL;
|
|
54510
55023
|
}
|
|
54511
55024
|
const mainPosition = this.getters.getCellPosition(cell.id);
|
|
54512
55025
|
const result = this.getters.getFirstPivotFunction(cell.compiledFormula.tokens);
|
|
54513
55026
|
if (!result) {
|
|
54514
|
-
return
|
|
55027
|
+
return EMPTY_PIVOT_CELL;
|
|
54515
55028
|
}
|
|
54516
55029
|
const { functionName, args } = result;
|
|
54517
55030
|
if (functionName === "PIVOT") {
|
|
54518
55031
|
const formulaId = args[0];
|
|
54519
55032
|
if (!formulaId) {
|
|
54520
|
-
return
|
|
55033
|
+
return EMPTY_PIVOT_CELL;
|
|
54521
55034
|
}
|
|
54522
55035
|
const pivotId = this.getters.getPivotId(formulaId.toString());
|
|
54523
55036
|
if (!pivotId) {
|
|
54524
|
-
return
|
|
55037
|
+
return EMPTY_PIVOT_CELL;
|
|
54525
55038
|
}
|
|
54526
55039
|
const pivot = this.getPivot(pivotId);
|
|
54527
55040
|
if (!pivot.isValid()) {
|
|
54528
|
-
return
|
|
55041
|
+
return EMPTY_PIVOT_CELL;
|
|
54529
55042
|
}
|
|
54530
55043
|
const includeTotal = args[2] === false ? false : undefined;
|
|
54531
55044
|
const includeColumnHeaders = args[3] === false ? false : undefined;
|
|
@@ -54534,22 +55047,28 @@ class PivotUIPlugin extends UIPlugin {
|
|
|
54534
55047
|
.getPivotCells(includeTotal, includeColumnHeaders);
|
|
54535
55048
|
const pivotCol = position.col - mainPosition.col;
|
|
54536
55049
|
const pivotRow = position.row - mainPosition.row;
|
|
54537
|
-
|
|
54538
|
-
|
|
54539
|
-
|
|
54540
|
-
}
|
|
54541
|
-
|
|
54542
|
-
|
|
54543
|
-
domain
|
|
54544
|
-
|
|
54545
|
-
|
|
55050
|
+
return pivotCells[pivotCol][pivotRow];
|
|
55051
|
+
}
|
|
55052
|
+
if (functionName === "PIVOT.HEADER" && args.at(-2) === "measure") {
|
|
55053
|
+
const domain = toPivotDomain(args.slice(1, -2).map((x) => `${x}`));
|
|
55054
|
+
return {
|
|
55055
|
+
type: "MEASURE_HEADER",
|
|
55056
|
+
domain,
|
|
55057
|
+
measure: args.at(-1)?.toString() || "",
|
|
55058
|
+
};
|
|
54546
55059
|
}
|
|
54547
|
-
|
|
54548
|
-
|
|
54549
|
-
|
|
55060
|
+
else if (functionName === "PIVOT.HEADER") {
|
|
55061
|
+
return {
|
|
55062
|
+
type: "HEADER",
|
|
55063
|
+
domain: toPivotDomain(args.slice(1).map((x) => `${x}`)),
|
|
55064
|
+
};
|
|
54550
55065
|
}
|
|
54551
|
-
const
|
|
54552
|
-
return {
|
|
55066
|
+
const [measure, ...domainArgs] = args.slice(1);
|
|
55067
|
+
return {
|
|
55068
|
+
type: "VALUE",
|
|
55069
|
+
domain: toPivotDomain(domainArgs.map((x) => `${x}`)),
|
|
55070
|
+
measure: measure?.toString() || "",
|
|
55071
|
+
};
|
|
54553
55072
|
}
|
|
54554
55073
|
getPivot(pivotId) {
|
|
54555
55074
|
return this.pivots[pivotId];
|
|
@@ -56420,43 +56939,6 @@ class DataCleanupPlugin extends UIPlugin {
|
|
|
56420
56939
|
}
|
|
56421
56940
|
}
|
|
56422
56941
|
|
|
56423
|
-
/**
|
|
56424
|
-
* Find and Replace Plugin
|
|
56425
|
-
*
|
|
56426
|
-
* This plugin is used in combination with the find_and_replace sidePanel
|
|
56427
|
-
* It is used to 'highlight' cells that match an input string according to
|
|
56428
|
-
* the given searchOptions. The second part of this plugin makes it possible
|
|
56429
|
-
* (again with the find_and_replace sidePanel), to replace the values that match
|
|
56430
|
-
* the search with a new value.
|
|
56431
|
-
*/
|
|
56432
|
-
class FindAndReplacePlugin extends UIPlugin {
|
|
56433
|
-
static getters = [];
|
|
56434
|
-
handle(cmd) {
|
|
56435
|
-
switch (cmd.type) {
|
|
56436
|
-
case "REPLACE_SEARCH":
|
|
56437
|
-
for (const match of cmd.matches) {
|
|
56438
|
-
this.replaceMatch(match, cmd.searchString, cmd.replaceWith, cmd.searchOptions);
|
|
56439
|
-
}
|
|
56440
|
-
break;
|
|
56441
|
-
}
|
|
56442
|
-
}
|
|
56443
|
-
replaceMatch(selectedMatch, searchString, replaceWith, searchOptions) {
|
|
56444
|
-
const cell = this.getters.getCell(selectedMatch);
|
|
56445
|
-
if (!cell?.content) {
|
|
56446
|
-
return;
|
|
56447
|
-
}
|
|
56448
|
-
if (cell?.isFormula && !searchOptions.searchFormulas) {
|
|
56449
|
-
return;
|
|
56450
|
-
}
|
|
56451
|
-
const searchRegex = getSearchRegex(searchString, searchOptions);
|
|
56452
|
-
const replaceRegex = new RegExp(searchRegex.source, searchRegex.flags + "g");
|
|
56453
|
-
const toReplace = this.getters.getCellText(selectedMatch, searchOptions.searchFormulas);
|
|
56454
|
-
const content = toReplace.replace(replaceRegex, replaceWith);
|
|
56455
|
-
const canonicalContent = canonicalizeNumberContent(content, this.getters.getLocale());
|
|
56456
|
-
this.dispatch("UPDATE_CELL", { ...selectedMatch, content: canonicalContent });
|
|
56457
|
-
}
|
|
56458
|
-
}
|
|
56459
|
-
|
|
56460
56942
|
class FormatPlugin extends UIPlugin {
|
|
56461
56943
|
// ---------------------------------------------------------------------------
|
|
56462
56944
|
// Command Handling
|
|
@@ -60271,7 +60753,6 @@ const featurePluginRegistry = new Registry()
|
|
|
60271
60753
|
.add("ui_sheet", SheetUIPlugin)
|
|
60272
60754
|
.add("ui_options", UIOptionsPlugin)
|
|
60273
60755
|
.add("autofill", AutofillPlugin)
|
|
60274
|
-
.add("find_and_replace", FindAndReplacePlugin)
|
|
60275
60756
|
.add("sort", SortPlugin)
|
|
60276
60757
|
.add("automatic_sum", AutomaticSumPlugin)
|
|
60277
60758
|
.add("format", FormatPlugin)
|
|
@@ -63832,6 +64313,9 @@ class SelectionStreamProcessorImpl {
|
|
|
63832
64313
|
getBackToDefault() {
|
|
63833
64314
|
this.stream.getBackToDefault();
|
|
63834
64315
|
}
|
|
64316
|
+
getAnchor() {
|
|
64317
|
+
return this.anchor;
|
|
64318
|
+
}
|
|
63835
64319
|
modifyAnchor(anchor, mode, options) {
|
|
63836
64320
|
const sheetId = this.getters.getActiveSheetId();
|
|
63837
64321
|
anchor = {
|
|
@@ -66795,6 +67279,7 @@ const registries = {
|
|
|
66795
67279
|
chartSidePanelComponentRegistry,
|
|
66796
67280
|
chartComponentRegistry,
|
|
66797
67281
|
chartRegistry,
|
|
67282
|
+
chartSubtypeRegistry,
|
|
66798
67283
|
topbarMenuRegistry,
|
|
66799
67284
|
topbarComponentRegistry,
|
|
66800
67285
|
clickableCellRegistry,
|
|
@@ -66903,6 +67388,7 @@ const components = {
|
|
|
66903
67388
|
GaugeChartDesignPanel,
|
|
66904
67389
|
ScorecardChartConfigPanel,
|
|
66905
67390
|
ScorecardChartDesignPanel,
|
|
67391
|
+
ChartTypePicker,
|
|
66906
67392
|
FigureComponent,
|
|
66907
67393
|
Menu,
|
|
66908
67394
|
Popover,
|
|
@@ -66952,11 +67438,12 @@ const constants = {
|
|
|
66952
67438
|
DEFAULT_LOCALE,
|
|
66953
67439
|
HIGHLIGHT_COLOR,
|
|
66954
67440
|
PIVOT_TABLE_CONFIG,
|
|
67441
|
+
ChartTerms,
|
|
66955
67442
|
};
|
|
66956
67443
|
|
|
66957
67444
|
export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
|
|
66958
67445
|
|
|
66959
67446
|
|
|
66960
|
-
__info__.version = "17.4.0-alpha.
|
|
66961
|
-
__info__.date = "2024-06-
|
|
66962
|
-
__info__.hash = "
|
|
67447
|
+
__info__.version = "17.4.0-alpha.7";
|
|
67448
|
+
__info__.date = "2024-06-21T08:42:22.370Z";
|
|
67449
|
+
__info__.hash = "a99db9a";
|